'no use strict'
!(function(window) {
  if (typeof window.window !== 'undefined' && window.document) { return }
  if (window.require && window.define) { return }

  if (!window.console) {
    window.console = function() {
      var msgs = Array.prototype.slice.call(arguments, 0)
      postMessage({ type: 'log', data: msgs })
    }
    window.console.error =
    window.console.warn =
    window.console.log =
    window.console.trace = window.console
  }
  window.window = window
  window.ace = window

  window.onerror = function(message, file, line, col, err) {
    postMessage({ type: 'error', data: {
      message: message,
      data: err.data,
      file: file,
      line: line,
      col: col,
      stack: err.stack
    }})
  }

  window.normalizeModule = function(parentId, moduleName) {
    // normalize plugin requires
    if (moduleName.indexOf('!') !== -1) {
      var chunks = moduleName.split('!')
      return window.normalizeModule(parentId, chunks[0]) + '!' + window.normalizeModule(parentId, chunks[1])
    }
    // normalize relative requires
    if (moduleName.charAt(0) == '.') {
      var base = parentId.split('/').slice(0, -1).join('/')
      moduleName = (base ? base + '/' : '') + moduleName

      while (moduleName.indexOf('.') !== -1 && previous != moduleName) {
        var previous = moduleName
        moduleName = moduleName.replace(/^\.\//, '').replace(/\/\.\//, '/').replace(/[^\/]+\/\.\.\//, '')
      }
    }

    return moduleName
  }

  window.require = function require(parentId, id) {
    if (!id) {
      id = parentId
      parentId = null
    }
    if (!id.charAt) { throw new Error('worker.js require() accepts only (parentId, id) as arguments') }

    id = window.normalizeModule(parentId, id)

    var module = window.require.modules[id]
    if (module) {
      if (!module.initialized) {
        module.initialized = true
        module.exports = module.factory().exports
      }
      return module.exports
    }

    if (!window.require.tlns) { return console.log('unable to load ' + id) }

    var path = resolveModuleId(id, window.require.tlns)
    if (path.slice(-3) != '.js') path += '.js'

    window.require.id = id
    window.require.modules[id] = {} // prevent infinite loop on broken modules
    importScripts(path)
    return window.require(parentId, id)
  }
  function resolveModuleId(id, paths) {
    var testPath = id, tail = ''
    while (testPath) {
      var alias = paths[testPath]
      if (typeof alias === 'string') {
        return alias + tail
      } else if (alias) {
        return alias.location.replace(/\/*$/, '/') + (tail || alias.main || alias.name)
      } else if (alias === false) {
        return ''
      }
      var i = testPath.lastIndexOf('/')
      if (i === -1) break
      tail = testPath.substr(i) + tail
      testPath = testPath.slice(0, i)
    }
    return id
  }
  window.require.modules = {}
  window.require.tlns = {}

  window.define = function(id, deps, factory) {
    if (arguments.length == 2) {
      factory = deps
      if (typeof id !== 'string') {
        deps = id
        id = window.require.id
      }
    } else if (arguments.length == 1) {
      factory = id
      deps = []
      id = window.require.id
    }

    if (typeof factory !== 'function') {
      window.require.modules[id] = {
        exports: factory,
        initialized: true
      }
      return
    }

    if (!deps.length)
    // If there is no dependencies, we inject "require", "exports" and
    // "module" as dependencies, to provide CommonJS compatibility.
    { deps = ['require', 'exports', 'module'] }

    var req = function(childId) {
      return window.require(id, childId)
    }

    window.require.modules[id] = {
      exports: {},
      factory: function() {
        var module = this
        var returnExports = factory.apply(this, deps.slice(0, factory.length).map(function(dep) {
          switch (dep) {
            // Because "require", "exports" and "module" aren't actual
            // dependencies, we must handle them seperately.
            case 'require': return req
            case 'exports': return module.exports
            case 'module': return module
              // But for all other dependencies, we can just go ahead and
              // require them.
            default: return req(dep)
          }
        }))
        if (returnExports) { module.exports = returnExports }
        return module
      }
    }
  }
  window.define.amd = {}
  require.tlns = {}
  window.initBaseUrls = function initBaseUrls(topLevelNamespaces) {
    for (var i in topLevelNamespaces) { require.tlns[i] = topLevelNamespaces[i] }
  }

  window.initSender = function initSender() {
    var EventEmitter = window.require('ace/lib/event_emitter').EventEmitter
    var oop = window.require('ace/lib/oop')

    var Sender = function() {};

    (function() {
      oop.implement(this, EventEmitter)

      this.callback = function(data, callbackId) {
        postMessage({
          type: 'call',
          id: callbackId,
          data: data
        })
      }

      this.emit = function(name, data) {
        postMessage({
          type: 'event',
          name: name,
          data: data
        })
      }
    }).call(Sender.prototype)

    return new Sender()
  }

  var main = window.main = null
  var sender = window.sender = null

  window.onmessage = function(e) {
    var msg = e.data
    if (msg.event && sender) {
      sender._signal(msg.event, msg.data)
    } else if (msg.command) {
      if (main[msg.command]) { main[msg.command].apply(main, msg.args) } else if (window[msg.command]) { window[msg.command].apply(window, msg.args) } else { throw new Error('Unknown command:' + msg.command) }
    } else if (msg.init) {
      window.initBaseUrls(msg.tlns)
      require('ace/lib/es5-shim')
      sender = window.sender = window.initSender()
      var clazz = require(msg.module)[msg.classname]
      main = window.main = new clazz(sender)
    }
  }
})(this)

define('ace/lib/oop', [], function(require, exports, module) {
  'use strict'

  exports.inherits = function(ctor, superCtor) {
    ctor.super_ = superCtor
    ctor.prototype = Object.create(superCtor.prototype, {
      constructor: {
        value: ctor,
        enumerable: false,
        writable: true,
        configurable: true
      }
    })
  }

  exports.mixin = function(obj, mixin) {
    for (var key in mixin) {
      obj[key] = mixin[key]
    }
    return obj
  }

  exports.implement = function(proto, mixin) {
    exports.mixin(proto, mixin)
  }
})

define('ace/lib/lang', [], function(require, exports, module) {
  'use strict'

  exports.last = function(a) {
    return a[a.length - 1]
  }

  exports.stringReverse = function(string) {
    return string.split('').reverse().join('')
  }

  exports.stringRepeat = function(string, count) {
    var result = ''
    while (count > 0) {
      if (count & 1) { result += string }

      if (count >>= 1) { string += string }
    }
    return result
  }

  var trimBeginRegexp = /^\s\s*/
  var trimEndRegexp = /\s\s*$/

  exports.stringTrimLeft = function(string) {
    return string.replace(trimBeginRegexp, '')
  }

  exports.stringTrimRight = function(string) {
    return string.replace(trimEndRegexp, '')
  }

  exports.copyObject = function(obj) {
    var copy = {}
    for (var key in obj) {
      copy[key] = obj[key]
    }
    return copy
  }

  exports.copyArray = function(array) {
    var copy = []
    for (var i = 0, l = array.length; i < l; i++) {
      if (array[i] && typeof array[i] === 'object') { copy[i] = this.copyObject(array[i]) } else { copy[i] = array[i] }
    }
    return copy
  }

  exports.deepCopy = function deepCopy(obj) {
    if (typeof obj !== 'object' || !obj) { return obj }
    var copy
    if (Array.isArray(obj)) {
      copy = []
      for (var key = 0; key < obj.length; key++) {
        copy[key] = deepCopy(obj[key])
      }
      return copy
    }
    if (Object.prototype.toString.call(obj) !== '[object Object]') { return obj }

    copy = {}
    for (var key in obj) { copy[key] = deepCopy(obj[key]) }
    return copy
  }

  exports.arrayToMap = function(arr) {
    var map = {}
    for (var i = 0; i < arr.length; i++) {
      map[arr[i]] = 1
    }
    return map
  }

  exports.createMap = function(props) {
    var map = Object.create(null)
    for (var i in props) {
      map[i] = props[i]
    }
    return map
  }
  exports.arrayRemove = function(array, value) {
    for (var i = 0; i <= array.length; i++) {
      if (value === array[i]) {
        array.splice(i, 1)
      }
    }
  }

  exports.escapeRegExp = function(str) {
    return str.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1')
  }

  exports.escapeHTML = function(str) {
    return ('' + str).replace(/&/g, '&#38;').replace(/"/g, '&#34;').replace(/'/g, '&#39;').replace(/</g, '&#60;')
  }

  exports.getMatchOffsets = function(string, regExp) {
    var matches = []

    string.replace(regExp, function(str) {
      matches.push({
        offset: arguments[arguments.length - 2],
        length: str.length
      })
    })

    return matches
  }
  exports.deferredCall = function(fcn) {
    var timer = null
    var callback = function() {
      timer = null
      fcn()
    }

    var deferred = function(timeout) {
      deferred.cancel()
      timer = setTimeout(callback, timeout || 0)
      return deferred
    }

    deferred.schedule = deferred

    deferred.call = function() {
      this.cancel()
      fcn()
      return deferred
    }

    deferred.cancel = function() {
      clearTimeout(timer)
      timer = null
      return deferred
    }

    deferred.isPending = function() {
      return timer
    }

    return deferred
  }

  exports.delayedCall = function(fcn, defaultTimeout) {
    var timer = null
    var callback = function() {
      timer = null
      fcn()
    }

    var _self = function(timeout) {
      if (timer == null) { timer = setTimeout(callback, timeout || defaultTimeout) }
    }

    _self.delay = function(timeout) {
      timer && clearTimeout(timer)
      timer = setTimeout(callback, timeout || defaultTimeout)
    }
    _self.schedule = _self

    _self.call = function() {
      this.cancel()
      fcn()
    }

    _self.cancel = function() {
      timer && clearTimeout(timer)
      timer = null
    }

    _self.isPending = function() {
      return timer
    }

    return _self
  }
})

define('ace/range', [], function(require, exports, module) {
  'use strict'
  var comparePoints = function(p1, p2) {
    return p1.row - p2.row || p1.column - p2.column
  }
  var Range = function(startRow, startColumn, endRow, endColumn) {
    this.start = {
      row: startRow,
      column: startColumn
    }

    this.end = {
      row: endRow,
      column: endColumn
    }
  };

  (function() {
    this.isEqual = function(range) {
      return this.start.row === range.start.row &&
            this.end.row === range.end.row &&
            this.start.column === range.start.column &&
            this.end.column === range.end.column
    }
    this.toString = function() {
      return ('Range: [' + this.start.row + '/' + this.start.column +
            '] -> [' + this.end.row + '/' + this.end.column + ']')
    }

    this.contains = function(row, column) {
      return this.compare(row, column) == 0
    }
    this.compareRange = function(range) {
      var cmp,
        end = range.end,
        start = range.start

      cmp = this.compare(end.row, end.column)
      if (cmp == 1) {
        cmp = this.compare(start.row, start.column)
        if (cmp == 1) {
          return 2
        } else if (cmp == 0) {
          return 1
        } else {
          return 0
        }
      } else if (cmp == -1) {
        return -2
      } else {
        cmp = this.compare(start.row, start.column)
        if (cmp == -1) {
          return -1
        } else if (cmp == 1) {
          return 42
        } else {
          return 0
        }
      }
    }
    this.comparePoint = function(p) {
      return this.compare(p.row, p.column)
    }
    this.containsRange = function(range) {
      return this.comparePoint(range.start) == 0 && this.comparePoint(range.end) == 0
    }
    this.intersects = function(range) {
      var cmp = this.compareRange(range)
      return (cmp == -1 || cmp == 0 || cmp == 1)
    }
    this.isEnd = function(row, column) {
      return this.end.row == row && this.end.column == column
    }
    this.isStart = function(row, column) {
      return this.start.row == row && this.start.column == column
    }
    this.setStart = function(row, column) {
      if (typeof row === 'object') {
        this.start.column = row.column
        this.start.row = row.row
      } else {
        this.start.row = row
        this.start.column = column
      }
    }
    this.setEnd = function(row, column) {
      if (typeof row === 'object') {
        this.end.column = row.column
        this.end.row = row.row
      } else {
        this.end.row = row
        this.end.column = column
      }
    }
    this.inside = function(row, column) {
      if (this.compare(row, column) == 0) {
        if (this.isEnd(row, column) || this.isStart(row, column)) {
          return false
        } else {
          return true
        }
      }
      return false
    }
    this.insideStart = function(row, column) {
      if (this.compare(row, column) == 0) {
        if (this.isEnd(row, column)) {
          return false
        } else {
          return true
        }
      }
      return false
    }
    this.insideEnd = function(row, column) {
      if (this.compare(row, column) == 0) {
        if (this.isStart(row, column)) {
          return false
        } else {
          return true
        }
      }
      return false
    }
    this.compare = function(row, column) {
      if (!this.isMultiLine()) {
        if (row === this.start.row) {
          return column < this.start.column ? -1 : (column > this.end.column ? 1 : 0)
        }
      }

      if (row < this.start.row) { return -1 }

      if (row > this.end.row) { return 1 }

      if (this.start.row === row) { return column >= this.start.column ? 0 : -1 }

      if (this.end.row === row) { return column <= this.end.column ? 0 : 1 }

      return 0
    }
    this.compareStart = function(row, column) {
      if (this.start.row == row && this.start.column == column) {
        return -1
      } else {
        return this.compare(row, column)
      }
    }
    this.compareEnd = function(row, column) {
      if (this.end.row == row && this.end.column == column) {
        return 1
      } else {
        return this.compare(row, column)
      }
    }
    this.compareInside = function(row, column) {
      if (this.end.row == row && this.end.column == column) {
        return 1
      } else if (this.start.row == row && this.start.column == column) {
        return -1
      } else {
        return this.compare(row, column)
      }
    }
    this.clipRows = function(firstRow, lastRow) {
      if (this.end.row > lastRow) { var end = { row: lastRow + 1, column: 0 } } else if (this.end.row < firstRow) { var end = { row: firstRow, column: 0 } }

      if (this.start.row > lastRow) { var start = { row: lastRow + 1, column: 0 } } else if (this.start.row < firstRow) { var start = { row: firstRow, column: 0 } }

      return Range.fromPoints(start || this.start, end || this.end)
    }
    this.extend = function(row, column) {
      var cmp = this.compare(row, column)

      if (cmp == 0) { return this } else if (cmp == -1) { var start = { row: row, column: column } } else { var end = { row: row, column: column } }

      return Range.fromPoints(start || this.start, end || this.end)
    }

    this.isEmpty = function() {
      return (this.start.row === this.end.row && this.start.column === this.end.column)
    }
    this.isMultiLine = function() {
      return (this.start.row !== this.end.row)
    }
    this.clone = function() {
      return Range.fromPoints(this.start, this.end)
    }
    this.collapseRows = function() {
      if (this.end.column == 0) { return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row - 1), 0) } else { return new Range(this.start.row, 0, this.end.row, 0) }
    }
    this.toScreenRange = function(session) {
      var screenPosStart = session.documentToScreenPosition(this.start)
      var screenPosEnd = session.documentToScreenPosition(this.end)

      return new Range(
        screenPosStart.row, screenPosStart.column,
        screenPosEnd.row, screenPosEnd.column
      )
    }
    this.moveBy = function(row, column) {
      this.start.row += row
      this.start.column += column
      this.end.row += row
      this.end.column += column
    }
  }).call(Range.prototype)
  Range.fromPoints = function(start, end) {
    return new Range(start.row, start.column, end.row, end.column)
  }
  Range.comparePoints = comparePoints

  Range.comparePoints = function(p1, p2) {
    return p1.row - p2.row || p1.column - p2.column
  }

  exports.Range = Range
})

define('ace/apply_delta', [], function(require, exports, module) {
  'use strict'

  function throwDeltaError(delta, errorText) {
    console.log('Invalid Delta:', delta)
    throw 'Invalid Delta: ' + errorText
  }

  function positionInDocument(docLines, position) {
    return position.row >= 0 && position.row < docLines.length &&
           position.column >= 0 && position.column <= docLines[position.row].length
  }

  function validateDelta(docLines, delta) {
    if (delta.action != 'insert' && delta.action != 'remove') { throwDeltaError(delta, "delta.action must be 'insert' or 'remove'") }
    if (!(delta.lines instanceof Array)) { throwDeltaError(delta, 'delta.lines must be an Array') }
    if (!delta.start || !delta.end) { throwDeltaError(delta, 'delta.start/end must be an present') }
    var start = delta.start
    if (!positionInDocument(docLines, delta.start)) { throwDeltaError(delta, 'delta.start must be contained in document') }
    var end = delta.end
    if (delta.action == 'remove' && !positionInDocument(docLines, end)) { throwDeltaError(delta, "delta.end must contained in document for 'remove' actions") }
    var numRangeRows = end.row - start.row
    var numRangeLastLineChars = (end.column - (numRangeRows == 0 ? start.column : 0))
    if (numRangeRows != delta.lines.length - 1 || delta.lines[numRangeRows].length != numRangeLastLineChars) { throwDeltaError(delta, 'delta.range must match delta lines') }
  }

  exports.applyDelta = function(docLines, delta, doNotValidate) {
    var row = delta.start.row
    var startColumn = delta.start.column
    var line = docLines[row] || ''
    switch (delta.action) {
      case 'insert':
        var lines = delta.lines
        if (lines.length === 1) {
          docLines[row] = line.substring(0, startColumn) + delta.lines[0] + line.substring(startColumn)
        } else {
          var args = [row, 1].concat(delta.lines)
          docLines.splice.apply(docLines, args)
          docLines[row] = line.substring(0, startColumn) + docLines[row]
          docLines[row + delta.lines.length - 1] += line.substring(startColumn)
        }
        break
      case 'remove':
        var endColumn = delta.end.column
        var endRow = delta.end.row
        if (row === endRow) {
          docLines[row] = line.substring(0, startColumn) + line.substring(endColumn)
        } else {
          docLines.splice(
            row, endRow - row + 1,
            line.substring(0, startColumn) + docLines[endRow].substring(endColumn)
          )
        }
        break
    }
  }
})

define('ace/lib/event_emitter', [], function(require, exports, module) {
  'use strict'

  var EventEmitter = {}
  var stopPropagation = function() { this.propagationStopped = true }
  var preventDefault = function() { this.defaultPrevented = true }

  EventEmitter._emit =
EventEmitter._dispatchEvent = function(eventName, e) {
  this._eventRegistry || (this._eventRegistry = {})
  this._defaultHandlers || (this._defaultHandlers = {})

  var listeners = this._eventRegistry[eventName] || []
  var defaultHandler = this._defaultHandlers[eventName]
  if (!listeners.length && !defaultHandler) { return }

  if (typeof e !== 'object' || !e) { e = {} }

  if (!e.type) { e.type = eventName }
  if (!e.stopPropagation) { e.stopPropagation = stopPropagation }
  if (!e.preventDefault) { e.preventDefault = preventDefault }

  listeners = listeners.slice()
  for (var i = 0; i < listeners.length; i++) {
    listeners[i](e, this)
    if (e.propagationStopped) { break }
  }

  if (defaultHandler && !e.defaultPrevented) { return defaultHandler(e, this) }
}

  EventEmitter._signal = function(eventName, e) {
    var listeners = (this._eventRegistry || {})[eventName]
    if (!listeners) { return }
    listeners = listeners.slice()
    for (var i = 0; i < listeners.length; i++) { listeners[i](e, this) }
  }

  EventEmitter.once = function(eventName, callback) {
    var _self = this
    callback && this.addEventListener(eventName, function newCallback() {
      _self.removeEventListener(eventName, newCallback)
      callback.apply(null, arguments)
    })
  }

  EventEmitter.setDefaultHandler = function(eventName, callback) {
    var handlers = this._defaultHandlers
    if (!handlers) { handlers = this._defaultHandlers = { _disabled_: {}} }

    if (handlers[eventName]) {
      var old = handlers[eventName]
      var disabled = handlers._disabled_[eventName]
      if (!disabled) { handlers._disabled_[eventName] = disabled = [] }
      disabled.push(old)
      var i = disabled.indexOf(callback)
      if (i != -1) { disabled.splice(i, 1) }
    }
    handlers[eventName] = callback
  }
  EventEmitter.removeDefaultHandler = function(eventName, callback) {
    var handlers = this._defaultHandlers
    if (!handlers) { return }
    var disabled = handlers._disabled_[eventName]

    if (handlers[eventName] == callback) {
      if (disabled) { this.setDefaultHandler(eventName, disabled.pop()) }
    } else if (disabled) {
      var i = disabled.indexOf(callback)
      if (i != -1) { disabled.splice(i, 1) }
    }
  }

  EventEmitter.on =
EventEmitter.addEventListener = function(eventName, callback, capturing) {
  this._eventRegistry = this._eventRegistry || {}

  var listeners = this._eventRegistry[eventName]
  if (!listeners) { listeners = this._eventRegistry[eventName] = [] }

  if (listeners.indexOf(callback) == -1) { listeners[capturing ? 'unshift' : 'push'](callback) }
  return callback
}

  EventEmitter.off =
EventEmitter.removeListener =
EventEmitter.removeEventListener = function(eventName, callback) {
  this._eventRegistry = this._eventRegistry || {}

  var listeners = this._eventRegistry[eventName]
  if (!listeners) { return }

  var index = listeners.indexOf(callback)
  if (index !== -1) { listeners.splice(index, 1) }
}

  EventEmitter.removeAllListeners = function(eventName) {
    if (this._eventRegistry) this._eventRegistry[eventName] = []
  }

  exports.EventEmitter = EventEmitter
})

define('ace/anchor', [], function(require, exports, module) {
  'use strict'

  var oop = require('./lib/oop')
  var EventEmitter = require('./lib/event_emitter').EventEmitter

  var Anchor = exports.Anchor = function(doc, row, column) {
    this.$onChange = this.onChange.bind(this)
    this.attach(doc)

    if (typeof column === 'undefined') { this.setPosition(row.row, row.column) } else { this.setPosition(row, column) }
  };

  (function() {
    oop.implement(this, EventEmitter)
    this.getPosition = function() {
      return this.$clipPositionToDocument(this.row, this.column)
    }
    this.getDocument = function() {
      return this.document
    }
    this.$insertRight = false
    this.onChange = function(delta) {
      if (delta.start.row == delta.end.row && delta.start.row != this.row) { return }

      if (delta.start.row > this.row) { return }

      var point = $getTransformedPoint(delta, { row: this.row, column: this.column }, this.$insertRight)
      this.setPosition(point.row, point.column, true)
    }

    function $pointsInOrder(point1, point2, equalPointsInOrder) {
      var bColIsAfter = equalPointsInOrder ? point1.column <= point2.column : point1.column < point2.column
      return (point1.row < point2.row) || (point1.row == point2.row && bColIsAfter)
    }

    function $getTransformedPoint(delta, point, moveIfEqual) {
      var deltaIsInsert = delta.action == 'insert'
      var deltaRowShift = (deltaIsInsert ? 1 : -1) * (delta.end.row - delta.start.row)
      var deltaColShift = (deltaIsInsert ? 1 : -1) * (delta.end.column - delta.start.column)
      var deltaStart = delta.start
      var deltaEnd = deltaIsInsert ? deltaStart : delta.end // Collapse insert range.
      if ($pointsInOrder(point, deltaStart, moveIfEqual)) {
        return {
          row: point.row,
          column: point.column
        }
      }
      if ($pointsInOrder(deltaEnd, point, !moveIfEqual)) {
        return {
          row: point.row + deltaRowShift,
          column: point.column + (point.row == deltaEnd.row ? deltaColShift : 0)
        }
      }
      return {
        row: deltaStart.row,
        column: deltaStart.column
      }
    }
    this.setPosition = function(row, column, noClip) {
      var pos
      if (noClip) {
        pos = {
          row: row,
          column: column
        }
      } else {
        pos = this.$clipPositionToDocument(row, column)
      }

      if (this.row == pos.row && this.column == pos.column) { return }

      var old = {
        row: this.row,
        column: this.column
      }

      this.row = pos.row
      this.column = pos.column
      this._signal('change', {
        old: old,
        value: pos
      })
    }
    this.detach = function() {
      this.document.removeEventListener('change', this.$onChange)
    }
    this.attach = function(doc) {
      this.document = doc || this.document
      this.document.on('change', this.$onChange)
    }
    this.$clipPositionToDocument = function(row, column) {
      var pos = {}

      if (row >= this.document.getLength()) {
        pos.row = Math.max(0, this.document.getLength() - 1)
        pos.column = this.document.getLine(pos.row).length
      } else if (row < 0) {
        pos.row = 0
        pos.column = 0
      } else {
        pos.row = row
        pos.column = Math.min(this.document.getLine(pos.row).length, Math.max(0, column))
      }

      if (column < 0) { pos.column = 0 }

      return pos
    }
  }).call(Anchor.prototype)
})

define('ace/document', [], function(require, exports, module) {
  'use strict'

  var oop = require('./lib/oop')
  var applyDelta = require('./apply_delta').applyDelta
  var EventEmitter = require('./lib/event_emitter').EventEmitter
  var Range = require('./range').Range
  var Anchor = require('./anchor').Anchor

  var Document = function(textOrLines) {
    this.$lines = ['']
    if (textOrLines.length === 0) {
      this.$lines = ['']
    } else if (Array.isArray(textOrLines)) {
      this.insertMergedLines({ row: 0, column: 0 }, textOrLines)
    } else {
      this.insert({ row: 0, column: 0 }, textOrLines)
    }
  };

  (function() {
    oop.implement(this, EventEmitter)
    this.setValue = function(text) {
      var len = this.getLength() - 1
      this.remove(new Range(0, 0, len, this.getLine(len).length))
      this.insert({ row: 0, column: 0 }, text)
    }
    this.getValue = function() {
      return this.getAllLines().join(this.getNewLineCharacter())
    }
    this.createAnchor = function(row, column) {
      return new Anchor(this, row, column)
    }
    if ('aaa'.split(/a/).length === 0) {
      this.$split = function(text) {
        return text.replace(/\r\n|\r/g, '\n').split('\n')
      }
    } else {
      this.$split = function(text) {
        return text.split(/\r\n|\r|\n/)
      }
    }

    this.$detectNewLine = function(text) {
      var match = text.match(/^.*?(\r\n|\r|\n)/m)
      this.$autoNewLine = match ? match[1] : '\n'
      this._signal('changeNewLineMode')
    }
    this.getNewLineCharacter = function() {
      switch (this.$newLineMode) {
        case 'windows':
          return '\r\n'
        case 'unix':
          return '\n'
        default:
          return this.$autoNewLine || '\n'
      }
    }

    this.$autoNewLine = ''
    this.$newLineMode = 'auto'
    this.setNewLineMode = function(newLineMode) {
      if (this.$newLineMode === newLineMode) { return }

      this.$newLineMode = newLineMode
      this._signal('changeNewLineMode')
    }
    this.getNewLineMode = function() {
      return this.$newLineMode
    }
    this.isNewLine = function(text) {
      return (text == '\r\n' || text == '\r' || text == '\n')
    }
    this.getLine = function(row) {
      return this.$lines[row] || ''
    }
    this.getLines = function(firstRow, lastRow) {
      return this.$lines.slice(firstRow, lastRow + 1)
    }
    this.getAllLines = function() {
      return this.getLines(0, this.getLength())
    }
    this.getLength = function() {
      return this.$lines.length
    }
    this.getTextRange = function(range) {
      return this.getLinesForRange(range).join(this.getNewLineCharacter())
    }
    this.getLinesForRange = function(range) {
      var lines
      if (range.start.row === range.end.row) {
        lines = [this.getLine(range.start.row).substring(range.start.column, range.end.column)]
      } else {
        lines = this.getLines(range.start.row, range.end.row)
        lines[0] = (lines[0] || '').substring(range.start.column)
        var l = lines.length - 1
        if (range.end.row - range.start.row == l) { lines[l] = lines[l].substring(0, range.end.column) }
      }
      return lines
    }
    this.insertLines = function(row, lines) {
      console.warn('Use of document.insertLines is deprecated. Use the insertFullLines method instead.')
      return this.insertFullLines(row, lines)
    }
    this.removeLines = function(firstRow, lastRow) {
      console.warn('Use of document.removeLines is deprecated. Use the removeFullLines method instead.')
      return this.removeFullLines(firstRow, lastRow)
    }
    this.insertNewLine = function(position) {
      console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead.")
      return this.insertMergedLines(position, ['', ''])
    }
    this.insert = function(position, text) {
      if (this.getLength() <= 1) { this.$detectNewLine(text) }

      return this.insertMergedLines(position, this.$split(text))
    }
    this.insertInLine = function(position, text) {
      var start = this.clippedPos(position.row, position.column)
      var end = this.pos(position.row, position.column + text.length)

      this.applyDelta({
        start: start,
        end: end,
        action: 'insert',
        lines: [text]
      }, true)

      return this.clonePos(end)
    }

    this.clippedPos = function(row, column) {
      var length = this.getLength()
      if (row === undefined) {
        row = length
      } else if (row < 0) {
        row = 0
      } else if (row >= length) {
        row = length - 1
        column = undefined
      }
      var line = this.getLine(row)
      if (column == undefined) { column = line.length }
      column = Math.min(Math.max(column, 0), line.length)
      return { row: row, column: column }
    }

    this.clonePos = function(pos) {
      return { row: pos.row, column: pos.column }
    }

    this.pos = function(row, column) {
      return { row: row, column: column }
    }

    this.$clipPosition = function(position) {
      var length = this.getLength()
      if (position.row >= length) {
        position.row = Math.max(0, length - 1)
        position.column = this.getLine(length - 1).length
      } else {
        position.row = Math.max(0, position.row)
        position.column = Math.min(Math.max(position.column, 0), this.getLine(position.row).length)
      }
      return position
    }
    this.insertFullLines = function(row, lines) {
      row = Math.min(Math.max(row, 0), this.getLength())
      var column = 0
      if (row < this.getLength()) {
        lines = lines.concat([''])
        column = 0
      } else {
        lines = [''].concat(lines)
        row--
        column = this.$lines[row].length
      }
      this.insertMergedLines({ row: row, column: column }, lines)
    }
    this.insertMergedLines = function(position, lines) {
      var start = this.clippedPos(position.row, position.column)
      var end = {
        row: start.row + lines.length - 1,
        column: (lines.length == 1 ? start.column : 0) + lines[lines.length - 1].length
      }

      this.applyDelta({
        start: start,
        end: end,
        action: 'insert',
        lines: lines
      })

      return this.clonePos(end)
    }
    this.remove = function(range) {
      var start = this.clippedPos(range.start.row, range.start.column)
      var end = this.clippedPos(range.end.row, range.end.column)
      this.applyDelta({
        start: start,
        end: end,
        action: 'remove',
        lines: this.getLinesForRange({ start: start, end: end })
      })
      return this.clonePos(start)
    }
    this.removeInLine = function(row, startColumn, endColumn) {
      var start = this.clippedPos(row, startColumn)
      var end = this.clippedPos(row, endColumn)

      this.applyDelta({
        start: start,
        end: end,
        action: 'remove',
        lines: this.getLinesForRange({ start: start, end: end })
      }, true)

      return this.clonePos(start)
    }
    this.removeFullLines = function(firstRow, lastRow) {
      firstRow = Math.min(Math.max(0, firstRow), this.getLength() - 1)
      lastRow = Math.min(Math.max(0, lastRow), this.getLength() - 1)
      var deleteFirstNewLine = lastRow == this.getLength() - 1 && firstRow > 0
      var deleteLastNewLine = lastRow < this.getLength() - 1
      var startRow = (deleteFirstNewLine ? firstRow - 1 : firstRow)
      var startCol = (deleteFirstNewLine ? this.getLine(startRow).length : 0)
      var endRow = (deleteLastNewLine ? lastRow + 1 : lastRow)
      var endCol = (deleteLastNewLine ? 0 : this.getLine(endRow).length)
      var range = new Range(startRow, startCol, endRow, endCol)
      var deletedLines = this.$lines.slice(firstRow, lastRow + 1)

      this.applyDelta({
        start: range.start,
        end: range.end,
        action: 'remove',
        lines: this.getLinesForRange(range)
      })
      return deletedLines
    }
    this.removeNewLine = function(row) {
      if (row < this.getLength() - 1 && row >= 0) {
        this.applyDelta({
          start: this.pos(row, this.getLine(row).length),
          end: this.pos(row + 1, 0),
          action: 'remove',
          lines: ['', '']
        })
      }
    }
    this.replace = function(range, text) {
      if (!(range instanceof Range)) { range = Range.fromPoints(range.start, range.end) }
      if (text.length === 0 && range.isEmpty()) { return range.start }
      if (text == this.getTextRange(range)) { return range.end }

      this.remove(range)
      var end
      if (text) {
        end = this.insert(range.start, text)
      } else {
        end = range.start
      }

      return end
    }
    this.applyDeltas = function(deltas) {
      for (var i = 0; i < deltas.length; i++) {
        this.applyDelta(deltas[i])
      }
    }
    this.revertDeltas = function(deltas) {
      for (var i = deltas.length - 1; i >= 0; i--) {
        this.revertDelta(deltas[i])
      }
    }
    this.applyDelta = function(delta, doNotValidate) {
      var isInsert = delta.action == 'insert'
      if (isInsert ? delta.lines.length <= 1 && !delta.lines[0]
        : !Range.comparePoints(delta.start, delta.end)) {
        return
      }

      if (isInsert && delta.lines.length > 20000) {
        this.$splitAndapplyLargeDelta(delta, 20000)
      } else {
        applyDelta(this.$lines, delta, doNotValidate)
        this._signal('change', delta)
      }
    }

    this.$splitAndapplyLargeDelta = function(delta, MAX) {
      var lines = delta.lines
      var l = lines.length - MAX + 1
      var row = delta.start.row
      var column = delta.start.column
      for (var from = 0, to = 0; from < l; from = to) {
        to += MAX - 1
        var chunk = lines.slice(from, to)
        chunk.push('')
        this.applyDelta({
          start: this.pos(row + from, column),
          end: this.pos(row + to, column = 0),
          action: delta.action,
          lines: chunk
        }, true)
      }
      delta.lines = lines.slice(from)
      delta.start.row = row + from
      delta.start.column = column
      this.applyDelta(delta, true)
    }
    this.revertDelta = function(delta) {
      this.applyDelta({
        start: this.clonePos(delta.start),
        end: this.clonePos(delta.end),
        action: (delta.action == 'insert' ? 'remove' : 'insert'),
        lines: delta.lines.slice()
      })
    }
    this.indexToPosition = function(index, startRow) {
      var lines = this.$lines || this.getAllLines()
      var newlineLength = this.getNewLineCharacter().length
      for (var i = startRow || 0, l = lines.length; i < l; i++) {
        index -= lines[i].length + newlineLength
        if (index < 0) { return { row: i, column: index + lines[i].length + newlineLength } }
      }
      return { row: l - 1, column: index + lines[l - 1].length + newlineLength }
    }
    this.positionToIndex = function(pos, startRow) {
      var lines = this.$lines || this.getAllLines()
      var newlineLength = this.getNewLineCharacter().length
      var index = 0
      var row = Math.min(pos.row, lines.length)
      for (var i = startRow || 0; i < row; ++i) { index += lines[i].length + newlineLength }

      return index + pos.column
    }
  }).call(Document.prototype)

  exports.Document = Document
})

define('ace/worker/mirror', [], function(require, exports, module) {
  'use strict'

  var Range = require('../range').Range
  var Document = require('../document').Document
  var lang = require('../lib/lang')

  var Mirror = exports.Mirror = function(sender) {
    this.sender = sender
    var doc = this.doc = new Document('')

    var deferredUpdate = this.deferredUpdate = lang.delayedCall(this.onUpdate.bind(this))

    var _self = this
    sender.on('change', function(e) {
      var data = e.data
      if (data[0].start) {
        doc.applyDeltas(data)
      } else {
        for (var i = 0; i < data.length; i += 2) {
          if (Array.isArray(data[i + 1])) {
            var d = { action: 'insert', start: data[i], lines: data[i + 1] }
          } else {
            var d = { action: 'remove', start: data[i], end: data[i + 1] }
          }
          doc.applyDelta(d, true)
        }
      }
      if (_self.$timeout) { return deferredUpdate.schedule(_self.$timeout) }
      _self.onUpdate()
    })
  };

  (function() {
    this.$timeout = 500

    this.setTimeout = function(timeout) {
      this.$timeout = timeout
    }

    this.setValue = function(value) {
      this.doc.setValue(value)
      this.deferredUpdate.schedule(this.$timeout)
    }

    this.getValue = function(callbackId) {
      this.sender.callback(this.doc.getValue(), callbackId)
    }

    this.onUpdate = function() {
    }

    this.isPending = function() {
      return this.deferredUpdate.isPending()
    }
  }).call(Mirror.prototype)
})

define('ace/mode/css/csslint', [], function(require, exports, module) {
  var parserlib = {};
  (function() {
    function EventTarget() {
      this._listeners = {}
    }

    EventTarget.prototype = {
      constructor: EventTarget,
      addListener: function(type, listener) {
        if (!this._listeners[type]) {
          this._listeners[type] = []
        }

        this._listeners[type].push(listener)
      },
      fire: function(event) {
        if (typeof event === 'string') {
          event = { type: event }
        }
        if (typeof event.target !== 'undefined') {
          event.target = this
        }

        if (typeof event.type === 'undefined') {
          throw new Error("Event object missing 'type' property.")
        }

        if (this._listeners[event.type]) {
          var listeners = this._listeners[event.type].concat()
          for (var i = 0, len = listeners.length; i < len; i++) {
            listeners[i].call(this, event)
          }
        }
      },
      removeListener: function(type, listener) {
        if (this._listeners[type]) {
          var listeners = this._listeners[type]
          for (var i = 0, len = listeners.length; i < len; i++) {
            if (listeners[i] === listener) {
              listeners.splice(i, 1)
              break
            }
          }
        }
      }
    }
    function StringReader(text) {
      this._input = text.replace(/\n\r?/g, '\n')
      this._line = 1
      this._col = 1
      this._cursor = 0
    }

    StringReader.prototype = {
      constructor: StringReader,
      getCol: function() {
        return this._col
      },
      getLine: function() {
        return this._line
      },
      eof: function() {
        return (this._cursor == this._input.length)
      },
      peek: function(count) {
        var c = null
        count = (typeof count === 'undefined' ? 1 : count)
        if (this._cursor < this._input.length) {
          c = this._input.charAt(this._cursor + count - 1)
        }

        return c
      },
      read: function() {
        var c = null
        if (this._cursor < this._input.length) {
          if (this._input.charAt(this._cursor) == '\n') {
            this._line++
            this._col = 1
          } else {
            this._col++
          }
          c = this._input.charAt(this._cursor++)
        }

        return c
      },
      mark: function() {
        this._bookmark = {
          cursor: this._cursor,
          line: this._line,
          col: this._col
        }
      },

      reset: function() {
        if (this._bookmark) {
          this._cursor = this._bookmark.cursor
          this._line = this._bookmark.line
          this._col = this._bookmark.col
          delete this._bookmark
        }
      },
      readTo: function(pattern) {
        var buffer = '',
          c
        while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) != buffer.length - pattern.length) {
          c = this.read()
          if (c) {
            buffer += c
          } else {
            throw new Error('Expected "' + pattern + '" at line ' + this._line + ', col ' + this._col + '.')
          }
        }

        return buffer
      },
      readWhile: function(filter) {
        var buffer = '',
          c = this.read()

        while (c !== null && filter(c)) {
          buffer += c
          c = this.read()
        }

        return buffer
      },
      readMatch: function(matcher) {
        var source = this._input.substring(this._cursor),
          value = null
        if (typeof matcher === 'string') {
          if (source.indexOf(matcher) === 0) {
            value = this.readCount(matcher.length)
          }
        } else if (matcher instanceof RegExp) {
          if (matcher.test(source)) {
            value = this.readCount(RegExp.lastMatch.length)
          }
        }

        return value
      },
      readCount: function(count) {
        var buffer = ''

        while (count--) {
          buffer += this.read()
        }

        return buffer
      }

    }
    function SyntaxError(message, line, col) {
      this.col = col
      this.line = line
      this.message = message
    }
    SyntaxError.prototype = new Error()
    function SyntaxUnit(text, line, col, type) {
      this.col = col
      this.line = line
      this.text = text
      this.type = type
    }
    SyntaxUnit.fromToken = function(token) {
      return new SyntaxUnit(token.value, token.startLine, token.startCol)
    }

    SyntaxUnit.prototype = {
      constructor: SyntaxUnit,
      valueOf: function() {
        return this.text
      },
      toString: function() {
        return this.text
      }

    }
    function TokenStreamBase(input, tokenData) {
      this._reader = input ? new StringReader(input.toString()) : null
      this._token = null
      this._tokenData = tokenData
      this._lt = []
      this._ltIndex = 0

      this._ltIndexCache = []
    }
    TokenStreamBase.createTokenData = function(tokens) {
      var nameMap = [],
        typeMap = {},
        tokenData = tokens.concat([]),
        i = 0,
        len = tokenData.length + 1

      tokenData.UNKNOWN = -1
      tokenData.unshift({ name: 'EOF' })

      for (; i < len; i++) {
        nameMap.push(tokenData[i].name)
        tokenData[tokenData[i].name] = i
        if (tokenData[i].text) {
          typeMap[tokenData[i].text] = i
        }
      }

      tokenData.name = function(tt) {
        return nameMap[tt]
      }

      tokenData.type = function(c) {
        return typeMap[c]
      }

      return tokenData
    }

    TokenStreamBase.prototype = {
      constructor: TokenStreamBase,
      match: function(tokenTypes, channel) {
        if (!(tokenTypes instanceof Array)) {
          tokenTypes = [tokenTypes]
        }

        var tt = this.get(channel),
          i = 0,
          len = tokenTypes.length

        while (i < len) {
          if (tt == tokenTypes[i++]) {
            return true
          }
        }
        this.unget()
        return false
      },
      mustMatch: function(tokenTypes, channel) {
        var token
        if (!(tokenTypes instanceof Array)) {
          tokenTypes = [tokenTypes]
        }

        if (!this.match.apply(this, arguments)) {
          token = this.LT(1)
          throw new SyntaxError('Expected ' + this._tokenData[tokenTypes[0]].name +
                ' at line ' + token.startLine + ', col ' + token.startCol + '.', token.startLine, token.startCol)
        }
      },
      advance: function(tokenTypes, channel) {
        while (this.LA(0) !== 0 && !this.match(tokenTypes, channel)) {
          this.get()
        }

        return this.LA(0)
      },
      get: function(channel) {
        var tokenInfo = this._tokenData,
          reader = this._reader,
          value,
          i = 0,
          len = tokenInfo.length,
          found = false,
          token,
          info
        if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length) {
          i++
          this._token = this._lt[this._ltIndex++]
          info = tokenInfo[this._token.type]
          while ((info.channel !== undefined && channel !== info.channel) &&
                    this._ltIndex < this._lt.length) {
            this._token = this._lt[this._ltIndex++]
            info = tokenInfo[this._token.type]
            i++
          }
          if ((info.channel === undefined || channel === info.channel) &&
                    this._ltIndex <= this._lt.length) {
            this._ltIndexCache.push(i)
            return this._token.type
          }
        }
        token = this._getToken()
        if (token.type > -1 && !tokenInfo[token.type].hide) {
          token.channel = tokenInfo[token.type].channel
          this._token = token
          this._lt.push(token)
          this._ltIndexCache.push(this._lt.length - this._ltIndex + i)
          if (this._lt.length > 5) {
            this._lt.shift()
          }
          if (this._ltIndexCache.length > 5) {
            this._ltIndexCache.shift()
          }
          this._ltIndex = this._lt.length
        }
        info = tokenInfo[token.type]
        if (info &&
                (info.hide ||
                (info.channel !== undefined && channel !== info.channel))) {
          return this.get(channel)
        } else {
          return token.type
        }
      },
      LA: function(index) {
        var total = index,
          tt
        if (index > 0) {
          if (index > 5) {
            throw new Error('Too much lookahead.')
          }
          while (total) {
            tt = this.get()
            total--
          }
          while (total < index) {
            this.unget()
            total++
          }
        } else if (index < 0) {
          if (this._lt[this._ltIndex + index]) {
            tt = this._lt[this._ltIndex + index].type
          } else {
            throw new Error('Too much lookbehind.')
          }
        } else {
          tt = this._token.type
        }

        return tt
      },
      LT: function(index) {
        this.LA(index)
        return this._lt[this._ltIndex + index - 1]
      },
      peek: function() {
        return this.LA(1)
      },
      token: function() {
        return this._token
      },
      tokenName: function(tokenType) {
        if (tokenType < 0 || tokenType > this._tokenData.length) {
          return 'UNKNOWN_TOKEN'
        } else {
          return this._tokenData[tokenType].name
        }
      },
      tokenType: function(tokenName) {
        return this._tokenData[tokenName] || -1
      },
      unget: function() {
        if (this._ltIndexCache.length) {
          this._ltIndex -= this._ltIndexCache.pop()// --;
          this._token = this._lt[this._ltIndex - 1]
        } else {
          throw new Error('Too much lookahead.')
        }
      }

    }

    parserlib.util = {
      StringReader: StringReader,
      SyntaxError: SyntaxError,
      SyntaxUnit: SyntaxUnit,
      EventTarget: EventTarget,
      TokenStreamBase: TokenStreamBase
    }
  })();
  (function() {
    var EventTarget = parserlib.util.EventTarget,
      TokenStreamBase = parserlib.util.TokenStreamBase,
      StringReader = parserlib.util.StringReader,
      SyntaxError = parserlib.util.SyntaxError,
      SyntaxUnit = parserlib.util.SyntaxUnit

    var Colors = {
      aliceblue: '#f0f8ff',
      antiquewhite: '#faebd7',
      aqua: '#00ffff',
      aquamarine: '#7fffd4',
      azure: '#f0ffff',
      beige: '#f5f5dc',
      bisque: '#ffe4c4',
      black: '#000000',
      blanchedalmond: '#ffebcd',
      blue: '#0000ff',
      blueviolet: '#8a2be2',
      brown: '#a52a2a',
      burlywood: '#deb887',
      cadetblue: '#5f9ea0',
      chartreuse: '#7fff00',
      chocolate: '#d2691e',
      coral: '#ff7f50',
      cornflowerblue: '#6495ed',
      cornsilk: '#fff8dc',
      crimson: '#dc143c',
      cyan: '#00ffff',
      darkblue: '#00008b',
      darkcyan: '#008b8b',
      darkgoldenrod: '#b8860b',
      darkgray: '#a9a9a9',
      darkgrey: '#a9a9a9',
      darkgreen: '#006400',
      darkkhaki: '#bdb76b',
      darkmagenta: '#8b008b',
      darkolivegreen: '#556b2f',
      darkorange: '#ff8c00',
      darkorchid: '#9932cc',
      darkred: '#8b0000',
      darksalmon: '#e9967a',
      darkseagreen: '#8fbc8f',
      darkslateblue: '#483d8b',
      darkslategray: '#2f4f4f',
      darkslategrey: '#2f4f4f',
      darkturquoise: '#00ced1',
      darkviolet: '#9400d3',
      deeppink: '#ff1493',
      deepskyblue: '#00bfff',
      dimgray: '#696969',
      dimgrey: '#696969',
      dodgerblue: '#1e90ff',
      firebrick: '#b22222',
      floralwhite: '#fffaf0',
      forestgreen: '#228b22',
      fuchsia: '#ff00ff',
      gainsboro: '#dcdcdc',
      ghostwhite: '#f8f8ff',
      gold: '#ffd700',
      goldenrod: '#daa520',
      gray: '#808080',
      grey: '#808080',
      green: '#008000',
      greenyellow: '#adff2f',
      honeydew: '#f0fff0',
      hotpink: '#ff69b4',
      indianred: '#cd5c5c',
      indigo: '#4b0082',
      ivory: '#fffff0',
      khaki: '#f0e68c',
      lavender: '#e6e6fa',
      lavenderblush: '#fff0f5',
      lawngreen: '#7cfc00',
      lemonchiffon: '#fffacd',
      lightblue: '#add8e6',
      lightcoral: '#f08080',
      lightcyan: '#e0ffff',
      lightgoldenrodyellow: '#fafad2',
      lightgray: '#d3d3d3',
      lightgrey: '#d3d3d3',
      lightgreen: '#90ee90',
      lightpink: '#ffb6c1',
      lightsalmon: '#ffa07a',
      lightseagreen: '#20b2aa',
      lightskyblue: '#87cefa',
      lightslategray: '#778899',
      lightslategrey: '#778899',
      lightsteelblue: '#b0c4de',
      lightyellow: '#ffffe0',
      lime: '#00ff00',
      limegreen: '#32cd32',
      linen: '#faf0e6',
      magenta: '#ff00ff',
      maroon: '#800000',
      mediumaquamarine: '#66cdaa',
      mediumblue: '#0000cd',
      mediumorchid: '#ba55d3',
      mediumpurple: '#9370d8',
      mediumseagreen: '#3cb371',
      mediumslateblue: '#7b68ee',
      mediumspringgreen: '#00fa9a',
      mediumturquoise: '#48d1cc',
      mediumvioletred: '#c71585',
      midnightblue: '#191970',
      mintcream: '#f5fffa',
      mistyrose: '#ffe4e1',
      moccasin: '#ffe4b5',
      navajowhite: '#ffdead',
      navy: '#000080',
      oldlace: '#fdf5e6',
      olive: '#808000',
      olivedrab: '#6b8e23',
      orange: '#ffa500',
      orangered: '#ff4500',
      orchid: '#da70d6',
      palegoldenrod: '#eee8aa',
      palegreen: '#98fb98',
      paleturquoise: '#afeeee',
      palevioletred: '#d87093',
      papayawhip: '#ffefd5',
      peachpuff: '#ffdab9',
      peru: '#cd853f',
      pink: '#ffc0cb',
      plum: '#dda0dd',
      powderblue: '#b0e0e6',
      purple: '#800080',
      red: '#ff0000',
      rosybrown: '#bc8f8f',
      royalblue: '#4169e1',
      saddlebrown: '#8b4513',
      salmon: '#fa8072',
      sandybrown: '#f4a460',
      seagreen: '#2e8b57',
      seashell: '#fff5ee',
      sienna: '#a0522d',
      silver: '#c0c0c0',
      skyblue: '#87ceeb',
      slateblue: '#6a5acd',
      slategray: '#708090',
      slategrey: '#708090',
      snow: '#fffafa',
      springgreen: '#00ff7f',
      steelblue: '#4682b4',
      tan: '#d2b48c',
      teal: '#008080',
      thistle: '#d8bfd8',
      tomato: '#ff6347',
      turquoise: '#40e0d0',
      violet: '#ee82ee',
      wheat: '#f5deb3',
      white: '#ffffff',
      whitesmoke: '#f5f5f5',
      yellow: '#ffff00',
      yellowgreen: '#9acd32',
      activeBorder: 'Active window border.',
      activecaption: 'Active window caption.',
      appworkspace: 'Background color of multiple document interface.',
      background: 'Desktop background.',
      buttonface: 'The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.',
      buttonhighlight: 'The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.',
      buttonshadow: 'The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.',
      buttontext: 'Text on push buttons.',
      captiontext: 'Text in caption, size box, and scrollbar arrow box.',
      graytext: 'Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.',
      greytext: 'Greyed (disabled) text. This color is set to #000 if the current display driver does not support a solid grey color.',
      highlight: 'Item(s) selected in a control.',
      highlighttext: 'Text of item(s) selected in a control.',
      inactiveborder: 'Inactive window border.',
      inactivecaption: 'Inactive window caption.',
      inactivecaptiontext: 'Color of text in an inactive caption.',
      infobackground: 'Background color for tooltip controls.',
      infotext: 'Text color for tooltip controls.',
      menu: 'Menu background.',
      menutext: 'Text in menus.',
      scrollbar: 'Scroll bar gray area.',
      threeddarkshadow: 'The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.',
      threedface: 'The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.',
      threedhighlight: 'The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.',
      threedlightshadow: 'The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.',
      threedshadow: 'The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.',
      window: 'Window background.',
      windowframe: 'Window frame.',
      windowtext: 'Text in windows.'
    }
    function Combinator(text, line, col) {
      SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE)
      this.type = 'unknown'
      if (/^\s+$/.test(text)) {
        this.type = 'descendant'
      } else if (text == '>') {
        this.type = 'child'
      } else if (text == '+') {
        this.type = 'adjacent-sibling'
      } else if (text == '~') {
        this.type = 'sibling'
      }
    }

    Combinator.prototype = new SyntaxUnit()
    Combinator.prototype.constructor = Combinator
    function MediaFeature(name, value) {
      SyntaxUnit.call(this, '(' + name + (value !== null ? ':' + value : '') + ')', name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE)
      this.name = name
      this.value = value
    }

    MediaFeature.prototype = new SyntaxUnit()
    MediaFeature.prototype.constructor = MediaFeature
    function MediaQuery(modifier, mediaType, features, line, col) {
      SyntaxUnit.call(this, (modifier ? modifier + ' ' : '') + (mediaType || '') + (mediaType && features.length > 0 ? ' and ' : '') + features.join(' and '), line, col, Parser.MEDIA_QUERY_TYPE)
      this.modifier = modifier
      this.mediaType = mediaType
      this.features = features
    }

    MediaQuery.prototype = new SyntaxUnit()
    MediaQuery.prototype.constructor = MediaQuery
    function Parser(options) {
      EventTarget.call(this)

      this.options = options || {}

      this._tokenStream = null
    }
    Parser.DEFAULT_TYPE = 0
    Parser.COMBINATOR_TYPE = 1
    Parser.MEDIA_FEATURE_TYPE = 2
    Parser.MEDIA_QUERY_TYPE = 3
    Parser.PROPERTY_NAME_TYPE = 4
    Parser.PROPERTY_VALUE_TYPE = 5
    Parser.PROPERTY_VALUE_PART_TYPE = 6
    Parser.SELECTOR_TYPE = 7
    Parser.SELECTOR_PART_TYPE = 8
    Parser.SELECTOR_SUB_PART_TYPE = 9

    Parser.prototype = (function() {
      var proto = new EventTarget(), // new prototype
        prop,
        additions = {
          constructor: Parser,
          DEFAULT_TYPE: 0,
          COMBINATOR_TYPE: 1,
          MEDIA_FEATURE_TYPE: 2,
          MEDIA_QUERY_TYPE: 3,
          PROPERTY_NAME_TYPE: 4,
          PROPERTY_VALUE_TYPE: 5,
          PROPERTY_VALUE_PART_TYPE: 6,
          SELECTOR_TYPE: 7,
          SELECTOR_PART_TYPE: 8,
          SELECTOR_SUB_PART_TYPE: 9,
          _stylesheet: function() {
            var tokenStream = this._tokenStream,
              charset = null,
              count,
              token,
              tt

            this.fire('startstylesheet')
            this._charset()

            this._skipCruft()
            while (tokenStream.peek() == Tokens.IMPORT_SYM) {
              this._import()
              this._skipCruft()
            }
            while (tokenStream.peek() == Tokens.NAMESPACE_SYM) {
              this._namespace()
              this._skipCruft()
            }
            tt = tokenStream.peek()
            while (tt > Tokens.EOF) {
              try {
                switch (tt) {
                  case Tokens.MEDIA_SYM:
                    this._media()
                    this._skipCruft()
                    break
                  case Tokens.PAGE_SYM:
                    this._page()
                    this._skipCruft()
                    break
                  case Tokens.FONT_FACE_SYM:
                    this._font_face()
                    this._skipCruft()
                    break
                  case Tokens.KEYFRAMES_SYM:
                    this._keyframes()
                    this._skipCruft()
                    break
                  case Tokens.VIEWPORT_SYM:
                    this._viewport()
                    this._skipCruft()
                    break
                  case Tokens.UNKNOWN_SYM: // unknown @ rule
                    tokenStream.get()
                    if (!this.options.strict) {
                      this.fire({
                        type: 'error',
                        error: null,
                        message: 'Unknown @ rule: ' + tokenStream.LT(0).value + '.',
                        line: tokenStream.LT(0).startLine,
                        col: tokenStream.LT(0).startCol
                      })
                      count = 0
                      while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) == Tokens.LBRACE) {
                        count++ // keep track of nesting depth
                      }

                      while (count) {
                        tokenStream.advance([Tokens.RBRACE])
                        count--
                      }
                    } else {
                      throw new SyntaxError('Unknown @ rule.', tokenStream.LT(0).startLine, tokenStream.LT(0).startCol)
                    }
                    break
                  case Tokens.S:
                    this._readWhitespace()
                    break
                  default:
                    if (!this._ruleset()) {
                      switch (tt) {
                        case Tokens.CHARSET_SYM:
                          token = tokenStream.LT(1)
                          this._charset(false)
                          throw new SyntaxError('@charset not allowed here.', token.startLine, token.startCol)
                        case Tokens.IMPORT_SYM:
                          token = tokenStream.LT(1)
                          this._import(false)
                          throw new SyntaxError('@import not allowed here.', token.startLine, token.startCol)
                        case Tokens.NAMESPACE_SYM:
                          token = tokenStream.LT(1)
                          this._namespace(false)
                          throw new SyntaxError('@namespace not allowed here.', token.startLine, token.startCol)
                        default:
                          tokenStream.get() // get the last token
                          this._unexpectedToken(tokenStream.token())
                      }
                    }
                }
              } catch (ex) {
                if (ex instanceof SyntaxError && !this.options.strict) {
                  this.fire({
                    type: 'error',
                    error: ex,
                    message: ex.message,
                    line: ex.line,
                    col: ex.col
                  })
                } else {
                  throw ex
                }
              }

              tt = tokenStream.peek()
            }

            if (tt != Tokens.EOF) {
              this._unexpectedToken(tokenStream.token())
            }

            this.fire('endstylesheet')
          },

          _charset: function(emit) {
            var tokenStream = this._tokenStream,
              charset,
              token,
              line,
              col

            if (tokenStream.match(Tokens.CHARSET_SYM)) {
              line = tokenStream.token().startLine
              col = tokenStream.token().startCol

              this._readWhitespace()
              tokenStream.mustMatch(Tokens.STRING)

              token = tokenStream.token()
              charset = token.value

              this._readWhitespace()
              tokenStream.mustMatch(Tokens.SEMICOLON)

              if (emit !== false) {
                this.fire({
                  type: 'charset',
                  charset: charset,
                  line: line,
                  col: col
                })
              }
            }
          },

          _import: function(emit) {
            var tokenStream = this._tokenStream,
              tt,
              uri,
              importToken,
              mediaList = []
            tokenStream.mustMatch(Tokens.IMPORT_SYM)
            importToken = tokenStream.token()
            this._readWhitespace()

            tokenStream.mustMatch([Tokens.STRING, Tokens.URI])
            uri = tokenStream.token().value.replace(/^(?:url\()?["']?([^"']+?)["']?\)?$/, '$1')

            this._readWhitespace()

            mediaList = this._media_query_list()
            tokenStream.mustMatch(Tokens.SEMICOLON)
            this._readWhitespace()

            if (emit !== false) {
              this.fire({
                type: 'import',
                uri: uri,
                media: mediaList,
                line: importToken.startLine,
                col: importToken.startCol
              })
            }
          },

          _namespace: function(emit) {
            var tokenStream = this._tokenStream,
              line,
              col,
              prefix,
              uri
            tokenStream.mustMatch(Tokens.NAMESPACE_SYM)
            line = tokenStream.token().startLine
            col = tokenStream.token().startCol
            this._readWhitespace()
            if (tokenStream.match(Tokens.IDENT)) {
              prefix = tokenStream.token().value
              this._readWhitespace()
            }

            tokenStream.mustMatch([Tokens.STRING, Tokens.URI])
            uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, '$1')

            this._readWhitespace()
            tokenStream.mustMatch(Tokens.SEMICOLON)
            this._readWhitespace()

            if (emit !== false) {
              this.fire({
                type: 'namespace',
                prefix: prefix,
                uri: uri,
                line: line,
                col: col
              })
            }
          },

          _media: function() {
            var tokenStream = this._tokenStream,
              line,
              col,
              mediaList//       = [];
            tokenStream.mustMatch(Tokens.MEDIA_SYM)
            line = tokenStream.token().startLine
            col = tokenStream.token().startCol

            this._readWhitespace()

            mediaList = this._media_query_list()

            tokenStream.mustMatch(Tokens.LBRACE)
            this._readWhitespace()

            this.fire({
              type: 'startmedia',
              media: mediaList,
              line: line,
              col: col
            })

            while (true) {
              if (tokenStream.peek() == Tokens.PAGE_SYM) {
                this._page()
              } else if (tokenStream.peek() == Tokens.FONT_FACE_SYM) {
                this._font_face()
              } else if (tokenStream.peek() == Tokens.VIEWPORT_SYM) {
                this._viewport()
              } else if (!this._ruleset()) {
                break
              }
            }

            tokenStream.mustMatch(Tokens.RBRACE)
            this._readWhitespace()

            this.fire({
              type: 'endmedia',
              media: mediaList,
              line: line,
              col: col
            })
          },
          _media_query_list: function() {
            var tokenStream = this._tokenStream,
              mediaList = []

            this._readWhitespace()

            if (tokenStream.peek() == Tokens.IDENT || tokenStream.peek() == Tokens.LPAREN) {
              mediaList.push(this._media_query())
            }

            while (tokenStream.match(Tokens.COMMA)) {
              this._readWhitespace()
              mediaList.push(this._media_query())
            }

            return mediaList
          },
          _media_query: function() {
            var tokenStream = this._tokenStream,
              type = null,
              ident = null,
              token = null,
              expressions = []

            if (tokenStream.match(Tokens.IDENT)) {
              ident = tokenStream.token().value.toLowerCase()
              if (ident != 'only' && ident != 'not') {
                tokenStream.unget()
                ident = null
              } else {
                token = tokenStream.token()
              }
            }

            this._readWhitespace()

            if (tokenStream.peek() == Tokens.IDENT) {
              type = this._media_type()
              if (token === null) {
                token = tokenStream.token()
              }
            } else if (tokenStream.peek() == Tokens.LPAREN) {
              if (token === null) {
                token = tokenStream.LT(1)
              }
              expressions.push(this._media_expression())
            }

            if (type === null && expressions.length === 0) {
              return null
            } else {
              this._readWhitespace()
              while (tokenStream.match(Tokens.IDENT)) {
                if (tokenStream.token().value.toLowerCase() != 'and') {
                  this._unexpectedToken(tokenStream.token())
                }

                this._readWhitespace()
                expressions.push(this._media_expression())
              }
            }

            return new MediaQuery(ident, type, expressions, token.startLine, token.startCol)
          },
          _media_type: function() {
            return this._media_feature()
          },
          _media_expression: function() {
            var tokenStream = this._tokenStream,
              feature = null,
              token,
              expression = null

            tokenStream.mustMatch(Tokens.LPAREN)
            this._readWhitespace()

            feature = this._media_feature()
            this._readWhitespace()

            if (tokenStream.match(Tokens.COLON)) {
              this._readWhitespace()
              token = tokenStream.LT(1)
              expression = this._expression()
            }

            tokenStream.mustMatch(Tokens.RPAREN)
            this._readWhitespace()

            return new MediaFeature(feature, (expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null))
          },
          _media_feature: function() {
            var tokenStream = this._tokenStream

            tokenStream.mustMatch(Tokens.IDENT)

            return SyntaxUnit.fromToken(tokenStream.token())
          },
          _page: function() {
            var tokenStream = this._tokenStream,
              line,
              col,
              identifier = null,
              pseudoPage = null
            tokenStream.mustMatch(Tokens.PAGE_SYM)
            line = tokenStream.token().startLine
            col = tokenStream.token().startCol

            this._readWhitespace()

            if (tokenStream.match(Tokens.IDENT)) {
              identifier = tokenStream.token().value
              if (identifier.toLowerCase() === 'auto') {
                this._unexpectedToken(tokenStream.token())
              }
            }
            if (tokenStream.peek() == Tokens.COLON) {
              pseudoPage = this._pseudo_page()
            }

            this._readWhitespace()

            this.fire({
              type: 'startpage',
              id: identifier,
              pseudo: pseudoPage,
              line: line,
              col: col
            })

            this._readDeclarations(true, true)

            this.fire({
              type: 'endpage',
              id: identifier,
              pseudo: pseudoPage,
              line: line,
              col: col
            })
          },
          _margin: function() {
            var tokenStream = this._tokenStream,
              line,
              col,
              marginSym = this._margin_sym()

            if (marginSym) {
              line = tokenStream.token().startLine
              col = tokenStream.token().startCol

              this.fire({
                type: 'startpagemargin',
                margin: marginSym,
                line: line,
                col: col
              })

              this._readDeclarations(true)

              this.fire({
                type: 'endpagemargin',
                margin: marginSym,
                line: line,
                col: col
              })
              return true
            } else {
              return false
            }
          },
          _margin_sym: function() {
            var tokenStream = this._tokenStream

            if (tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM,
              Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM,
              Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM,
              Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM,
              Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM,
              Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM,
              Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) {
              return SyntaxUnit.fromToken(tokenStream.token())
            } else {
              return null
            }
          },

          _pseudo_page: function() {
            var tokenStream = this._tokenStream

            tokenStream.mustMatch(Tokens.COLON)
            tokenStream.mustMatch(Tokens.IDENT)
            return tokenStream.token().value
          },

          _font_face: function() {
            var tokenStream = this._tokenStream,
              line,
              col
            tokenStream.mustMatch(Tokens.FONT_FACE_SYM)
            line = tokenStream.token().startLine
            col = tokenStream.token().startCol

            this._readWhitespace()

            this.fire({
              type: 'startfontface',
              line: line,
              col: col
            })

            this._readDeclarations(true)

            this.fire({
              type: 'endfontface',
              line: line,
              col: col
            })
          },

          _viewport: function() {
            var tokenStream = this._tokenStream,
              line,
              col

            tokenStream.mustMatch(Tokens.VIEWPORT_SYM)
            line = tokenStream.token().startLine
            col = tokenStream.token().startCol

            this._readWhitespace()

            this.fire({
              type: 'startviewport',
              line: line,
              col: col
            })

            this._readDeclarations(true)

            this.fire({
              type: 'endviewport',
              line: line,
              col: col
            })
          },

          _operator: function(inFunction) {
            var tokenStream = this._tokenStream,
              token = null

            if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) ||
                    (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))) {
              token = tokenStream.token()
              this._readWhitespace()
            }
            return token ? PropertyValuePart.fromToken(token) : null
          },

          _combinator: function() {
            var tokenStream = this._tokenStream,
              value = null,
              token

            if (tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])) {
              token = tokenStream.token()
              value = new Combinator(token.value, token.startLine, token.startCol)
              this._readWhitespace()
            }

            return value
          },

          _unary_operator: function() {
            var tokenStream = this._tokenStream

            if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])) {
              return tokenStream.token().value
            } else {
              return null
            }
          },

          _property: function() {
            var tokenStream = this._tokenStream,
              value = null,
              hack = null,
              tokenValue,
              token,
              line,
              col
            if (tokenStream.peek() == Tokens.STAR && this.options.starHack) {
              tokenStream.get()
              token = tokenStream.token()
              hack = token.value
              line = token.startLine
              col = token.startCol
            }

            if (tokenStream.match(Tokens.IDENT)) {
              token = tokenStream.token()
              tokenValue = token.value
              if (tokenValue.charAt(0) == '_' && this.options.underscoreHack) {
                hack = '_'
                tokenValue = tokenValue.substring(1)
              }

              value = new PropertyName(tokenValue, hack, (line || token.startLine), (col || token.startCol))
              this._readWhitespace()
            }

            return value
          },
          _ruleset: function() {
            var tokenStream = this._tokenStream,
              tt,
              selectors
            try {
              selectors = this._selectors_group()
            } catch (ex) {
              if (ex instanceof SyntaxError && !this.options.strict) {
                this.fire({
                  type: 'error',
                  error: ex,
                  message: ex.message,
                  line: ex.line,
                  col: ex.col
                })
                tt = tokenStream.advance([Tokens.RBRACE])
                if (tt == Tokens.RBRACE) {
                } else {
                  throw ex
                }
              } else {
                throw ex
              }
              return true
            }
            if (selectors) {
              this.fire({
                type: 'startrule',
                selectors: selectors,
                line: selectors[0].line,
                col: selectors[0].col
              })

              this._readDeclarations(true)

              this.fire({
                type: 'endrule',
                selectors: selectors,
                line: selectors[0].line,
                col: selectors[0].col
              })
            }

            return selectors
          },
          _selectors_group: function() {
            var tokenStream = this._tokenStream,
              selectors = [],
              selector

            selector = this._selector()
            if (selector !== null) {
              selectors.push(selector)
              while (tokenStream.match(Tokens.COMMA)) {
                this._readWhitespace()
                selector = this._selector()
                if (selector !== null) {
                  selectors.push(selector)
                } else {
                  this._unexpectedToken(tokenStream.LT(1))
                }
              }
            }

            return selectors.length ? selectors : null
          },
          _selector: function() {
            var tokenStream = this._tokenStream,
              selector = [],
              nextSelector = null,
              combinator = null,
              ws = null
            nextSelector = this._simple_selector_sequence()
            if (nextSelector === null) {
              return null
            }

            selector.push(nextSelector)

            do {
              combinator = this._combinator()

              if (combinator !== null) {
                selector.push(combinator)
                nextSelector = this._simple_selector_sequence()
                if (nextSelector === null) {
                  this._unexpectedToken(tokenStream.LT(1))
                } else {
                  selector.push(nextSelector)
                }
              } else {
                if (this._readWhitespace()) {
                  ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol)
                  combinator = this._combinator()
                  nextSelector = this._simple_selector_sequence()
                  if (nextSelector === null) {
                    if (combinator !== null) {
                      this._unexpectedToken(tokenStream.LT(1))
                    }
                  } else {
                    if (combinator !== null) {
                      selector.push(combinator)
                    } else {
                      selector.push(ws)
                    }

                    selector.push(nextSelector)
                  }
                } else {
                  break
                }
              }
            } while (true)

            return new Selector(selector, selector[0].line, selector[0].col)
          },
          _simple_selector_sequence: function() {
            var tokenStream = this._tokenStream,
              elementName = null,
              modifiers = [],
              selectorText = '',
              components = [
                function() {
                  return tokenStream.match(Tokens.HASH)
                    ? new SelectorSubPart(tokenStream.token().value, 'id', tokenStream.token().startLine, tokenStream.token().startCol)
                    : null
                },
                this._class,
                this._attrib,
                this._pseudo,
                this._negation
              ],
              i = 0,
              len = components.length,
              component = null,
              found = false,
              line,
              col
            line = tokenStream.LT(1).startLine
            col = tokenStream.LT(1).startCol

            elementName = this._type_selector()
            if (!elementName) {
              elementName = this._universal()
            }

            if (elementName !== null) {
              selectorText += elementName
            }

            while (true) {
              if (tokenStream.peek() === Tokens.S) {
                break
              }
              while (i < len && component === null) {
                component = components[i++].call(this)
              }

              if (component === null) {
                if (selectorText === '') {
                  return null
                } else {
                  break
                }
              } else {
                i = 0
                modifiers.push(component)
                selectorText += component.toString()
                component = null
              }
            }

            return selectorText !== ''
              ? new SelectorPart(elementName, modifiers, selectorText, line, col)
              : null
          },
          _type_selector: function() {
            var tokenStream = this._tokenStream,
              ns = this._namespace_prefix(),
              elementName = this._element_name()

            if (!elementName) {
              if (ns) {
                tokenStream.unget()
                if (ns.length > 1) {
                  tokenStream.unget()
                }
              }

              return null
            } else {
              if (ns) {
                elementName.text = ns + elementName.text
                elementName.col -= ns.length
              }
              return elementName
            }
          },
          _class: function() {
            var tokenStream = this._tokenStream,
              token

            if (tokenStream.match(Tokens.DOT)) {
              tokenStream.mustMatch(Tokens.IDENT)
              token = tokenStream.token()
              return new SelectorSubPart('.' + token.value, 'class', token.startLine, token.startCol - 1)
            } else {
              return null
            }
          },
          _element_name: function() {
            var tokenStream = this._tokenStream,
              token

            if (tokenStream.match(Tokens.IDENT)) {
              token = tokenStream.token()
              return new SelectorSubPart(token.value, 'elementName', token.startLine, token.startCol)
            } else {
              return null
            }
          },
          _namespace_prefix: function() {
            var tokenStream = this._tokenStream,
              value = ''
            if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE) {
              if (tokenStream.match([Tokens.IDENT, Tokens.STAR])) {
                value += tokenStream.token().value
              }

              tokenStream.mustMatch(Tokens.PIPE)
              value += '|'
            }

            return value.length ? value : null
          },
          _universal: function() {
            var tokenStream = this._tokenStream,
              value = '',
              ns

            ns = this._namespace_prefix()
            if (ns) {
              value += ns
            }

            if (tokenStream.match(Tokens.STAR)) {
              value += '*'
            }

            return value.length ? value : null
          },
          _attrib: function() {
            var tokenStream = this._tokenStream,
              value = null,
              ns,
              token

            if (tokenStream.match(Tokens.LBRACKET)) {
              token = tokenStream.token()
              value = token.value
              value += this._readWhitespace()

              ns = this._namespace_prefix()

              if (ns) {
                value += ns
              }

              tokenStream.mustMatch(Tokens.IDENT)
              value += tokenStream.token().value
              value += this._readWhitespace()

              if (tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH,
                Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])) {
                value += tokenStream.token().value
                value += this._readWhitespace()

                tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING])
                value += tokenStream.token().value
                value += this._readWhitespace()
              }

              tokenStream.mustMatch(Tokens.RBRACKET)

              return new SelectorSubPart(value + ']', 'attribute', token.startLine, token.startCol)
            } else {
              return null
            }
          },
          _pseudo: function() {
            var tokenStream = this._tokenStream,
              pseudo = null,
              colons = ':',
              line,
              col

            if (tokenStream.match(Tokens.COLON)) {
              if (tokenStream.match(Tokens.COLON)) {
                colons += ':'
              }

              if (tokenStream.match(Tokens.IDENT)) {
                pseudo = tokenStream.token().value
                line = tokenStream.token().startLine
                col = tokenStream.token().startCol - colons.length
              } else if (tokenStream.peek() == Tokens.FUNCTION) {
                line = tokenStream.LT(1).startLine
                col = tokenStream.LT(1).startCol - colons.length
                pseudo = this._functional_pseudo()
              }

              if (pseudo) {
                pseudo = new SelectorSubPart(colons + pseudo, 'pseudo', line, col)
              }
            }

            return pseudo
          },
          _functional_pseudo: function() {
            var tokenStream = this._tokenStream,
              value = null

            if (tokenStream.match(Tokens.FUNCTION)) {
              value = tokenStream.token().value
              value += this._readWhitespace()
              value += this._expression()
              tokenStream.mustMatch(Tokens.RPAREN)
              value += ')'
            }

            return value
          },
          _expression: function() {
            var tokenStream = this._tokenStream,
              value = ''

            while (tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION,
              Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH,
              Tokens.FREQ, Tokens.ANGLE, Tokens.TIME,
              Tokens.RESOLUTION, Tokens.SLASH])) {
              value += tokenStream.token().value
              value += this._readWhitespace()
            }

            return value.length ? value : null
          },
          _negation: function() {
            var tokenStream = this._tokenStream,
              line,
              col,
              value = '',
              arg,
              subpart = null

            if (tokenStream.match(Tokens.NOT)) {
              value = tokenStream.token().value
              line = tokenStream.token().startLine
              col = tokenStream.token().startCol
              value += this._readWhitespace()
              arg = this._negation_arg()
              value += arg
              value += this._readWhitespace()
              tokenStream.match(Tokens.RPAREN)
              value += tokenStream.token().value

              subpart = new SelectorSubPart(value, 'not', line, col)
              subpart.args.push(arg)
            }

            return subpart
          },
          _negation_arg: function() {
            var tokenStream = this._tokenStream,
              args = [
                this._type_selector,
                this._universal,
                function() {
                  return tokenStream.match(Tokens.HASH)
                    ? new SelectorSubPart(tokenStream.token().value, 'id', tokenStream.token().startLine, tokenStream.token().startCol)
                    : null
                },
                this._class,
                this._attrib,
                this._pseudo
              ],
              arg = null,
              i = 0,
              len = args.length,
              elementName,
              line,
              col,
              part

            line = tokenStream.LT(1).startLine
            col = tokenStream.LT(1).startCol

            while (i < len && arg === null) {
              arg = args[i].call(this)
              i++
            }
            if (arg === null) {
              this._unexpectedToken(tokenStream.LT(1))
            }
            if (arg.type == 'elementName') {
              part = new SelectorPart(arg, [], arg.toString(), line, col)
            } else {
              part = new SelectorPart(null, [arg], arg.toString(), line, col)
            }

            return part
          },

          _declaration: function() {
            var tokenStream = this._tokenStream,
              property = null,
              expr = null,
              prio = null,
              error = null,
              invalid = null,
              propertyName = ''

            property = this._property()
            if (property !== null) {
              tokenStream.mustMatch(Tokens.COLON)
              this._readWhitespace()

              expr = this._expr()
              if (!expr || expr.length === 0) {
                this._unexpectedToken(tokenStream.LT(1))
              }

              prio = this._prio()
              propertyName = property.toString()
              if (this.options.starHack && property.hack == '*' ||
                            this.options.underscoreHack && property.hack == '_') {
                propertyName = property.text
              }

              try {
                this._validateProperty(propertyName, expr)
              } catch (ex) {
                invalid = ex
              }

              this.fire({
                type: 'property',
                property: property,
                value: expr,
                important: prio,
                line: property.line,
                col: property.col,
                invalid: invalid
              })

              return true
            } else {
              return false
            }
          },

          _prio: function() {
            var tokenStream = this._tokenStream,
              result = tokenStream.match(Tokens.IMPORTANT_SYM)

            this._readWhitespace()
            return result
          },

          _expr: function(inFunction) {
            var tokenStream = this._tokenStream,
              values = [],
              value = null,
              operator = null

            value = this._term(inFunction)
            if (value !== null) {
              values.push(value)

              do {
                operator = this._operator(inFunction)
                if (operator) {
                  values.push(operator)
                } /* else {
                            values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
                            valueParts = [];
                        }*/

                value = this._term(inFunction)

                if (value === null) {
                  break
                } else {
                  values.push(value)
                }
              } while (true)
            }

            return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null
          },

          _term: function(inFunction) {
            var tokenStream = this._tokenStream,
              unary = null,
              value = null,
              endChar = null,
              token,
              line,
              col
            unary = this._unary_operator()
            if (unary !== null) {
              line = tokenStream.token().startLine
              col = tokenStream.token().startCol
            }
            if (tokenStream.peek() == Tokens.IE_FUNCTION && this.options.ieFilters) {
              value = this._ie_function()
              if (unary === null) {
                line = tokenStream.token().startLine
                col = tokenStream.token().startCol
              }
            } else if (inFunction && tokenStream.match([Tokens.LPAREN, Tokens.LBRACE, Tokens.LBRACKET])) {
              token = tokenStream.token()
              endChar = token.endChar
              value = token.value + this._expr(inFunction).text
              if (unary === null) {
                line = tokenStream.token().startLine
                col = tokenStream.token().startCol
              }
              tokenStream.mustMatch(Tokens.type(endChar))
              value += endChar
              this._readWhitespace()
            } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH,
              Tokens.ANGLE, Tokens.TIME,
              Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) {
              value = tokenStream.token().value
              if (unary === null) {
                line = tokenStream.token().startLine
                col = tokenStream.token().startCol
              }
              this._readWhitespace()
            } else {
              token = this._hexcolor()
              if (token === null) {
                if (unary === null) {
                  line = tokenStream.LT(1).startLine
                  col = tokenStream.LT(1).startCol
                }
                if (value === null) {
                  if (tokenStream.LA(3) == Tokens.EQUALS && this.options.ieFilters) {
                    value = this._ie_function()
                  } else {
                    value = this._function()
                  }
                }
              } else {
                value = token.value
                if (unary === null) {
                  line = token.startLine
                  col = token.startCol
                }
              }
            }

            return value !== null
              ? new PropertyValuePart(unary !== null ? unary + value : value, line, col)
              : null
          },

          _function: function() {
            var tokenStream = this._tokenStream,
              functionText = null,
              expr = null,
              lt

            if (tokenStream.match(Tokens.FUNCTION)) {
              functionText = tokenStream.token().value
              this._readWhitespace()
              expr = this._expr(true)
              functionText += expr
              if (this.options.ieFilters && tokenStream.peek() == Tokens.EQUALS) {
                do {
                  if (this._readWhitespace()) {
                    functionText += tokenStream.token().value
                  }
                  if (tokenStream.LA(0) == Tokens.COMMA) {
                    functionText += tokenStream.token().value
                  }

                  tokenStream.match(Tokens.IDENT)
                  functionText += tokenStream.token().value

                  tokenStream.match(Tokens.EQUALS)
                  functionText += tokenStream.token().value
                  lt = tokenStream.peek()
                  while (lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN) {
                    tokenStream.get()
                    functionText += tokenStream.token().value
                    lt = tokenStream.peek()
                  }
                } while (tokenStream.match([Tokens.COMMA, Tokens.S]))
              }
              tokenStream.match(Tokens.RPAREN)
              functionText += ')'
              this._readWhitespace()
            }

            return functionText
          },

          _ie_function: function() {
            var tokenStream = this._tokenStream,
              functionText = null,
              expr = null,
              lt
            if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])) {
              functionText = tokenStream.token().value

              do {
                if (this._readWhitespace()) {
                  functionText += tokenStream.token().value
                }
                if (tokenStream.LA(0) == Tokens.COMMA) {
                  functionText += tokenStream.token().value
                }

                tokenStream.match(Tokens.IDENT)
                functionText += tokenStream.token().value

                tokenStream.match(Tokens.EQUALS)
                functionText += tokenStream.token().value
                lt = tokenStream.peek()
                while (lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN) {
                  tokenStream.get()
                  functionText += tokenStream.token().value
                  lt = tokenStream.peek()
                }
              } while (tokenStream.match([Tokens.COMMA, Tokens.S]))

              tokenStream.match(Tokens.RPAREN)
              functionText += ')'
              this._readWhitespace()
            }

            return functionText
          },

          _hexcolor: function() {
            var tokenStream = this._tokenStream,
              token = null,
              color

            if (tokenStream.match(Tokens.HASH)) {
              token = tokenStream.token()
              color = token.value
              if (!/#[a-f0-9]{3,6}/i.test(color)) {
                throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ', col ' + token.startCol + '.', token.startLine, token.startCol)
              }
              this._readWhitespace()
            }

            return token
          },
          _keyframes: function() {
            var tokenStream = this._tokenStream,
              token,
              tt,
              name,
              prefix = ''

            tokenStream.mustMatch(Tokens.KEYFRAMES_SYM)
            token = tokenStream.token()
            if (/^@\-([^\-]+)\-/.test(token.value)) {
              prefix = RegExp.$1
            }

            this._readWhitespace()
            name = this._keyframe_name()

            this._readWhitespace()
            tokenStream.mustMatch(Tokens.LBRACE)

            this.fire({
              type: 'startkeyframes',
              name: name,
              prefix: prefix,
              line: token.startLine,
              col: token.startCol
            })

            this._readWhitespace()
            tt = tokenStream.peek()
            while (tt == Tokens.IDENT || tt == Tokens.PERCENTAGE) {
              this._keyframe_rule()
              this._readWhitespace()
              tt = tokenStream.peek()
            }

            this.fire({
              type: 'endkeyframes',
              name: name,
              prefix: prefix,
              line: token.startLine,
              col: token.startCol
            })

            this._readWhitespace()
            tokenStream.mustMatch(Tokens.RBRACE)
          },

          _keyframe_name: function() {
            var tokenStream = this._tokenStream,
              token

            tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING])
            return SyntaxUnit.fromToken(tokenStream.token())
          },

          _keyframe_rule: function() {
            var tokenStream = this._tokenStream,
              token,
              keyList = this._key_list()

            this.fire({
              type: 'startkeyframerule',
              keys: keyList,
              line: keyList[0].line,
              col: keyList[0].col
            })

            this._readDeclarations(true)

            this.fire({
              type: 'endkeyframerule',
              keys: keyList,
              line: keyList[0].line,
              col: keyList[0].col
            })
          },

          _key_list: function() {
            var tokenStream = this._tokenStream,
              token,
              key,
              keyList = []
            keyList.push(this._key())

            this._readWhitespace()

            while (tokenStream.match(Tokens.COMMA)) {
              this._readWhitespace()
              keyList.push(this._key())
              this._readWhitespace()
            }

            return keyList
          },

          _key: function() {
            var tokenStream = this._tokenStream,
              token

            if (tokenStream.match(Tokens.PERCENTAGE)) {
              return SyntaxUnit.fromToken(tokenStream.token())
            } else if (tokenStream.match(Tokens.IDENT)) {
              token = tokenStream.token()

              if (/from|to/i.test(token.value)) {
                return SyntaxUnit.fromToken(token)
              }

              tokenStream.unget()
            }
            this._unexpectedToken(tokenStream.LT(1))
          },
          _skipCruft: function() {
            while (this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])) {
            }
          },
          _readDeclarations: function(checkStart, readMargins) {
            var tokenStream = this._tokenStream,
              tt

            this._readWhitespace()

            if (checkStart) {
              tokenStream.mustMatch(Tokens.LBRACE)
            }

            this._readWhitespace()

            try {
              while (true) {
                if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())) {
                } else if (this._declaration()) {
                  if (!tokenStream.match(Tokens.SEMICOLON)) {
                    break
                  }
                } else {
                  break
                }
                this._readWhitespace()
              }

              tokenStream.mustMatch(Tokens.RBRACE)
              this._readWhitespace()
            } catch (ex) {
              if (ex instanceof SyntaxError && !this.options.strict) {
                this.fire({
                  type: 'error',
                  error: ex,
                  message: ex.message,
                  line: ex.line,
                  col: ex.col
                })
                tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE])
                if (tt == Tokens.SEMICOLON) {
                  this._readDeclarations(false, readMargins)
                } else if (tt != Tokens.RBRACE) {
                  throw ex
                }
              } else {
                throw ex
              }
            }
          },
          _readWhitespace: function() {
            var tokenStream = this._tokenStream,
              ws = ''

            while (tokenStream.match(Tokens.S)) {
              ws += tokenStream.token().value
            }

            return ws
          },
          _unexpectedToken: function(token) {
            throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ', col ' + token.startCol + '.', token.startLine, token.startCol)
          },
          _verifyEnd: function() {
            if (this._tokenStream.LA(1) != Tokens.EOF) {
              this._unexpectedToken(this._tokenStream.LT(1))
            }
          },
          _validateProperty: function(property, value) {
            Validation.validate(property, value)
          },
          parse: function(input) {
            this._tokenStream = new TokenStream(input, Tokens)
            this._stylesheet()
          },

          parseStyleSheet: function(input) {
            return this.parse(input)
          },

          parseMediaQuery: function(input) {
            this._tokenStream = new TokenStream(input, Tokens)
            var result = this._media_query()
            this._verifyEnd()
            return result
          },
          parsePropertyValue: function(input) {
            this._tokenStream = new TokenStream(input, Tokens)
            this._readWhitespace()

            var result = this._expr()
            this._readWhitespace()
            this._verifyEnd()
            return result
          },
          parseRule: function(input) {
            this._tokenStream = new TokenStream(input, Tokens)
            this._readWhitespace()

            var result = this._ruleset()
            this._readWhitespace()
            this._verifyEnd()
            return result
          },
          parseSelector: function(input) {
            this._tokenStream = new TokenStream(input, Tokens)
            this._readWhitespace()

            var result = this._selector()
            this._readWhitespace()
            this._verifyEnd()
            return result
          },
          parseStyleAttribute: function(input) {
            input += '}' // for error recovery in _readDeclarations()
            this._tokenStream = new TokenStream(input, Tokens)
            this._readDeclarations()
          }
        }
      for (prop in additions) {
        if (additions.hasOwnProperty(prop)) {
          proto[prop] = additions[prop]
        }
      }

      return proto
    }())
    var Properties = {
      'align-items': 'flex-start | flex-end | center | baseline | stretch',
      'align-content': 'flex-start | flex-end | center | space-between | space-around | stretch',
      'align-self': 'auto | flex-start | flex-end | center | baseline | stretch',
      '-webkit-align-items': 'flex-start | flex-end | center | baseline | stretch',
      '-webkit-align-content': 'flex-start | flex-end | center | space-between | space-around | stretch',
      '-webkit-align-self': 'auto | flex-start | flex-end | center | baseline | stretch',
      'alignment-adjust': 'auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | <percentage> | <length>',
      'alignment-baseline': 'baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical',
      'animation': 1,
      'animation-delay': { multi: '<time>', comma: true },
      'animation-direction': { multi: 'normal | reverse | alternate | alternate-reverse', comma: true },
      'animation-duration': { multi: '<time>', comma: true },
      'animation-fill-mode': { multi: 'none | forwards | backwards | both', comma: true },
      'animation-iteration-count': { multi: '<number> | infinite', comma: true },
      'animation-name': { multi: 'none | <ident>', comma: true },
      'animation-play-state': { multi: 'running | paused', comma: true },
      'animation-timing-function': 1,
      '-moz-animation-delay': { multi: '<time>', comma: true },
      '-moz-animation-direction': { multi: 'normal | reverse | alternate | alternate-reverse', comma: true },
      '-moz-animation-duration': { multi: '<time>', comma: true },
      '-moz-animation-iteration-count': { multi: '<number> | infinite', comma: true },
      '-moz-animation-name': { multi: 'none | <ident>', comma: true },
      '-moz-animation-play-state': { multi: 'running | paused', comma: true },

      '-ms-animation-delay': { multi: '<time>', comma: true },
      '-ms-animation-direction': { multi: 'normal | reverse | alternate | alternate-reverse', comma: true },
      '-ms-animation-duration': { multi: '<time>', comma: true },
      '-ms-animation-iteration-count': { multi: '<number> | infinite', comma: true },
      '-ms-animation-name': { multi: 'none | <ident>', comma: true },
      '-ms-animation-play-state': { multi: 'running | paused', comma: true },

      '-webkit-animation-delay': { multi: '<time>', comma: true },
      '-webkit-animation-direction': { multi: 'normal | reverse | alternate | alternate-reverse', comma: true },
      '-webkit-animation-duration': { multi: '<time>', comma: true },
      '-webkit-animation-fill-mode': { multi: 'none | forwards | backwards | both', comma: true },
      '-webkit-animation-iteration-count': { multi: '<number> | infinite', comma: true },
      '-webkit-animation-name': { multi: 'none | <ident>', comma: true },
      '-webkit-animation-play-state': { multi: 'running | paused', comma: true },

      '-o-animation-delay': { multi: '<time>', comma: true },
      '-o-animation-direction': { multi: 'normal | reverse | alternate | alternate-reverse', comma: true },
      '-o-animation-duration': { multi: '<time>', comma: true },
      '-o-animation-iteration-count': { multi: '<number> | infinite', comma: true },
      '-o-animation-name': { multi: 'none | <ident>', comma: true },
      '-o-animation-play-state': { multi: 'running | paused', comma: true },

      'appearance': 'icon | window | desktop | workspace | document | tooltip | dialog | button | push-button | hyperlink | radio-button | checkbox | menu-item | tab | menu | menubar | pull-down-menu | pop-up-menu | list-menu | radio-group | checkbox-group | outline-tree | range | field | combo-box | signature | password | normal | none | inherit',
      'azimuth': function(expression) {
        var simple = '<angle> | leftwards | rightwards | inherit',
          direction = 'left-side | far-left | left | center-left | center | center-right | right | far-right | right-side',
          behind = false,
          valid = false,
          part

        if (!ValidationTypes.isAny(expression, simple)) {
          if (ValidationTypes.isAny(expression, 'behind')) {
            behind = true
            valid = true
          }

          if (ValidationTypes.isAny(expression, direction)) {
            valid = true
            if (!behind) {
              ValidationTypes.isAny(expression, 'behind')
            }
          }
        }

        if (expression.hasNext()) {
          part = expression.next()
          if (valid) {
            throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
          } else {
            throw new ValidationError("Expected (<'azimuth'>) but found '" + part + "'.", part.line, part.col)
          }
        }
      },
      'backface-visibility': 'visible | hidden',
      'background': 1,
      'background-attachment': { multi: '<attachment>', comma: true },
      'background-clip': { multi: '<box>', comma: true },
      'background-color': '<color> | inherit',
      'background-image': { multi: '<bg-image>', comma: true },
      'background-origin': { multi: '<box>', comma: true },
      'background-position': { multi: '<bg-position>', comma: true },
      'background-repeat': { multi: '<repeat-style>' },
      'background-size': { multi: '<bg-size>', comma: true },
      'baseline-shift': 'baseline | sub | super | <percentage> | <length>',
      'behavior': 1,
      'binding': 1,
      'bleed': '<length>',
      'bookmark-label': '<content> | <attr> | <string>',
      'bookmark-level': 'none | <integer>',
      'bookmark-state': 'open | closed',
      'bookmark-target': 'none | <uri> | <attr>',
      'border': '<border-width> || <border-style> || <color>',
      'border-bottom': '<border-width> || <border-style> || <color>',
      'border-bottom-color': '<color> | inherit',
      'border-bottom-left-radius': '<x-one-radius>',
      'border-bottom-right-radius': '<x-one-radius>',
      'border-bottom-style': '<border-style>',
      'border-bottom-width': '<border-width>',
      'border-collapse': 'collapse | separate | inherit',
      'border-color': { multi: '<color> | inherit', max: 4 },
      'border-image': 1,
      'border-image-outset': { multi: '<length> | <number>', max: 4 },
      'border-image-repeat': { multi: 'stretch | repeat | round', max: 2 },
      'border-image-slice': function(expression) {
        var valid = false,
          numeric = '<number> | <percentage>',
          fill = false,
          count = 0,
          max = 4,
          part

        if (ValidationTypes.isAny(expression, 'fill')) {
          fill = true
          valid = true
        }

        while (expression.hasNext() && count < max) {
          valid = ValidationTypes.isAny(expression, numeric)
          if (!valid) {
            break
          }
          count++
        }

        if (!fill) {
          ValidationTypes.isAny(expression, 'fill')
        } else {
          valid = true
        }

        if (expression.hasNext()) {
          part = expression.next()
          if (valid) {
            throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
          } else {
            throw new ValidationError("Expected ([<number> | <percentage>]{1,4} && fill?) but found '" + part + "'.", part.line, part.col)
          }
        }
      },
      'border-image-source': '<image> | none',
      'border-image-width': { multi: '<length> | <percentage> | <number> | auto', max: 4 },
      'border-left': '<border-width> || <border-style> || <color>',
      'border-left-color': '<color> | inherit',
      'border-left-style': '<border-style>',
      'border-left-width': '<border-width>',
      'border-radius': function(expression) {
        var valid = false,
          simple = '<length> | <percentage> | inherit',
          slash = false,
          fill = false,
          count = 0,
          max = 8,
          part

        while (expression.hasNext() && count < max) {
          valid = ValidationTypes.isAny(expression, simple)
          if (!valid) {
            if (expression.peek() == '/' && count > 0 && !slash) {
              slash = true
              max = count + 5
              expression.next()
            } else {
              break
            }
          }
          count++
        }

        if (expression.hasNext()) {
          part = expression.next()
          if (valid) {
            throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
          } else {
            throw new ValidationError("Expected (<'border-radius'>) but found '" + part + "'.", part.line, part.col)
          }
        }
      },
      'border-right': '<border-width> || <border-style> || <color>',
      'border-right-color': '<color> | inherit',
      'border-right-style': '<border-style>',
      'border-right-width': '<border-width>',
      'border-spacing': { multi: '<length> | inherit', max: 2 },
      'border-style': { multi: '<border-style>', max: 4 },
      'border-top': '<border-width> || <border-style> || <color>',
      'border-top-color': '<color> | inherit',
      'border-top-left-radius': '<x-one-radius>',
      'border-top-right-radius': '<x-one-radius>',
      'border-top-style': '<border-style>',
      'border-top-width': '<border-width>',
      'border-width': { multi: '<border-width>', max: 4 },
      'bottom': '<margin-width> | inherit',
      '-moz-box-align': 'start | end | center | baseline | stretch',
      '-moz-box-decoration-break': 'slice |clone',
      '-moz-box-direction': 'normal | reverse | inherit',
      '-moz-box-flex': '<number>',
      '-moz-box-flex-group': '<integer>',
      '-moz-box-lines': 'single | multiple',
      '-moz-box-ordinal-group': '<integer>',
      '-moz-box-orient': 'horizontal | vertical | inline-axis | block-axis | inherit',
      '-moz-box-pack': 'start | end | center | justify',
      '-webkit-box-align': 'start | end | center | baseline | stretch',
      '-webkit-box-decoration-break': 'slice |clone',
      '-webkit-box-direction': 'normal | reverse | inherit',
      '-webkit-box-flex': '<number>',
      '-webkit-box-flex-group': '<integer>',
      '-webkit-box-lines': 'single | multiple',
      '-webkit-box-ordinal-group': '<integer>',
      '-webkit-box-orient': 'horizontal | vertical | inline-axis | block-axis | inherit',
      '-webkit-box-pack': 'start | end | center | justify',
      'box-shadow': function(expression) {
        var result = false,
          part

        if (!ValidationTypes.isAny(expression, 'none')) {
          Validation.multiProperty('<shadow>', expression, true, Infinity)
        } else {
          if (expression.hasNext()) {
            part = expression.next()
            throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
          }
        }
      },
      'box-sizing': 'content-box | border-box | inherit',
      'break-after': 'auto | always | avoid | left | right | page | column | avoid-page | avoid-column',
      'break-before': 'auto | always | avoid | left | right | page | column | avoid-page | avoid-column',
      'break-inside': 'auto | avoid | avoid-page | avoid-column',
      'caption-side': 'top | bottom | inherit',
      'clear': 'none | right | left | both | inherit',
      'clip': 1,
      'color': '<color> | inherit',
      'color-profile': 1,
      'column-count': '<integer> | auto', // http://www.w3.org/TR/css3-multicol/
      'column-fill': 'auto | balance',
      'column-gap': '<length> | normal',
      'column-rule': '<border-width> || <border-style> || <color>',
      'column-rule-color': '<color>',
      'column-rule-style': '<border-style>',
      'column-rule-width': '<border-width>',
      'column-span': 'none | all',
      'column-width': '<length> | auto',
      'columns': 1,
      'content': 1,
      'counter-increment': 1,
      'counter-reset': 1,
      'crop': '<shape> | auto',
      'cue': 'cue-after | cue-before | inherit',
      'cue-after': 1,
      'cue-before': 1,
      'cursor': 1,
      'direction': 'ltr | rtl | inherit',
      'display': 'inline | block | list-item | inline-block | table | inline-table | table-row-group | table-header-group | table-footer-group | table-row | table-column-group | table-column | table-cell | table-caption | grid | inline-grid | none | inherit | -moz-box | -moz-inline-block | -moz-inline-box | -moz-inline-grid | -moz-inline-stack | -moz-inline-table | -moz-grid | -moz-grid-group | -moz-grid-line | -moz-groupbox | -moz-deck | -moz-popup | -moz-stack | -moz-marker | -webkit-box | -webkit-inline-box | -ms-flexbox | -ms-inline-flexbox | flex | -webkit-flex | inline-flex | -webkit-inline-flex',
      'dominant-baseline': 1,
      'drop-initial-after-adjust': 'central | middle | after-edge | text-after-edge | ideographic | alphabetic | mathematical | <percentage> | <length>',
      'drop-initial-after-align': 'baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical',
      'drop-initial-before-adjust': 'before-edge | text-before-edge | central | middle | hanging | mathematical | <percentage> | <length>',
      'drop-initial-before-align': 'caps-height | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical',
      'drop-initial-size': 'auto | line | <length> | <percentage>',
      'drop-initial-value': 'initial | <integer>',
      'elevation': '<angle> | below | level | above | higher | lower | inherit',
      'empty-cells': 'show | hide | inherit',
      'filter': 1,
      'fit': 'fill | hidden | meet | slice',
      'fit-position': 1,
      'flex': '<flex>',
      'flex-basis': '<width>',
      'flex-direction': 'row | row-reverse | column | column-reverse',
      'flex-flow': '<flex-direction> || <flex-wrap>',
      'flex-grow': '<number>',
      'flex-shrink': '<number>',
      'flex-wrap': 'nowrap | wrap | wrap-reverse',
      '-webkit-flex': '<flex>',
      '-webkit-flex-basis': '<width>',
      '-webkit-flex-direction': 'row | row-reverse | column | column-reverse',
      '-webkit-flex-flow': '<flex-direction> || <flex-wrap>',
      '-webkit-flex-grow': '<number>',
      '-webkit-flex-shrink': '<number>',
      '-webkit-flex-wrap': 'nowrap | wrap | wrap-reverse',
      '-ms-flex': '<flex>',
      '-ms-flex-align': 'start | end | center | stretch | baseline',
      '-ms-flex-direction': 'row | row-reverse | column | column-reverse | inherit',
      '-ms-flex-order': '<number>',
      '-ms-flex-pack': 'start | end | center | justify',
      '-ms-flex-wrap': 'nowrap | wrap | wrap-reverse',
      'float': 'left | right | none | inherit',
      'float-offset': 1,
      'font': 1,
      'font-family': 1,
      'font-size': '<absolute-size> | <relative-size> | <length> | <percentage> | inherit',
      'font-size-adjust': '<number> | none | inherit',
      'font-stretch': 'normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit',
      'font-style': 'normal | italic | oblique | inherit',
      'font-variant': 'normal | small-caps | inherit',
      'font-weight': 'normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit',
      'grid-cell-stacking': 'columns | rows | layer',
      'grid-column': 1,
      'grid-columns': 1,
      'grid-column-align': 'start | end | center | stretch',
      'grid-column-sizing': 1,
      'grid-column-span': '<integer>',
      'grid-flow': 'none | rows | columns',
      'grid-layer': '<integer>',
      'grid-row': 1,
      'grid-rows': 1,
      'grid-row-align': 'start | end | center | stretch',
      'grid-row-span': '<integer>',
      'grid-row-sizing': 1,
      'hanging-punctuation': 1,
      'height': '<margin-width> | <content-sizing> | inherit',
      'hyphenate-after': '<integer> | auto',
      'hyphenate-before': '<integer> | auto',
      'hyphenate-character': '<string> | auto',
      'hyphenate-lines': 'no-limit | <integer>',
      'hyphenate-resource': 1,
      'hyphens': 'none | manual | auto',
      'icon': 1,
      'image-orientation': 'angle | auto',
      'image-rendering': 1,
      'image-resolution': 1,
      'inline-box-align': 'initial | last | <integer>',
      'justify-content': 'flex-start | flex-end | center | space-between | space-around',
      '-webkit-justify-content': 'flex-start | flex-end | center | space-between | space-around',
      'left': '<margin-width> | inherit',
      'letter-spacing': '<length> | normal | inherit',
      'line-height': '<number> | <length> | <percentage> | normal | inherit',
      'line-break': 'auto | loose | normal | strict',
      'line-stacking': 1,
      'line-stacking-ruby': 'exclude-ruby | include-ruby',
      'line-stacking-shift': 'consider-shifts | disregard-shifts',
      'line-stacking-strategy': 'inline-line-height | block-line-height | max-height | grid-height',
      'list-style': 1,
      'list-style-image': '<uri> | none | inherit',
      'list-style-position': 'inside | outside | inherit',
      'list-style-type': 'disc | circle | square | decimal | decimal-leading-zero | lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | georgian | lower-alpha | upper-alpha | none | inherit',
      'margin': { multi: '<margin-width> | inherit', max: 4 },
      'margin-bottom': '<margin-width> | inherit',
      'margin-left': '<margin-width> | inherit',
      'margin-right': '<margin-width> | inherit',
      'margin-top': '<margin-width> | inherit',
      'mark': 1,
      'mark-after': 1,
      'mark-before': 1,
      'marks': 1,
      'marquee-direction': 1,
      'marquee-play-count': 1,
      'marquee-speed': 1,
      'marquee-style': 1,
      'max-height': '<length> | <percentage> | <content-sizing> | none | inherit',
      'max-width': '<length> | <percentage> | <content-sizing> | none | inherit',
      'max-zoom': '<number> | <percentage> | auto',
      'min-height': '<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats | inherit',
      'min-width': '<length> | <percentage> | <content-sizing> | contain-floats | -moz-contain-floats | -webkit-contain-floats | inherit',
      'min-zoom': '<number> | <percentage> | auto',
      'move-to': 1,
      'nav-down': 1,
      'nav-index': 1,
      'nav-left': 1,
      'nav-right': 1,
      'nav-up': 1,
      'opacity': '<number> | inherit',
      'order': '<integer>',
      '-webkit-order': '<integer>',
      'orphans': '<integer> | inherit',
      'outline': 1,
      'outline-color': '<color> | invert | inherit',
      'outline-offset': 1,
      'outline-style': '<border-style> | inherit',
      'outline-width': '<border-width> | inherit',
      'overflow': 'visible | hidden | scroll | auto | inherit',
      'overflow-style': 1,
      'overflow-wrap': 'normal | break-word',
      'overflow-x': 1,
      'overflow-y': 1,
      'padding': { multi: '<padding-width> | inherit', max: 4 },
      'padding-bottom': '<padding-width> | inherit',
      'padding-left': '<padding-width> | inherit',
      'padding-right': '<padding-width> | inherit',
      'padding-top': '<padding-width> | inherit',
      'page': 1,
      'page-break-after': 'auto | always | avoid | left | right | inherit',
      'page-break-before': 'auto | always | avoid | left | right | inherit',
      'page-break-inside': 'auto | avoid | inherit',
      'page-policy': 1,
      'pause': 1,
      'pause-after': 1,
      'pause-before': 1,
      'perspective': 1,
      'perspective-origin': 1,
      'phonemes': 1,
      'pitch': 1,
      'pitch-range': 1,
      'play-during': 1,
      'pointer-events': 'auto | none | visiblePainted | visibleFill | visibleStroke | visible | painted | fill | stroke | all | inherit',
      'position': 'static | relative | absolute | fixed | inherit',
      'presentation-level': 1,
      'punctuation-trim': 1,
      'quotes': 1,
      'rendering-intent': 1,
      'resize': 1,
      'rest': 1,
      'rest-after': 1,
      'rest-before': 1,
      'richness': 1,
      'right': '<margin-width> | inherit',
      'rotation': 1,
      'rotation-point': 1,
      'ruby-align': 1,
      'ruby-overhang': 1,
      'ruby-position': 1,
      'ruby-span': 1,
      'size': 1,
      'speak': 'normal | none | spell-out | inherit',
      'speak-header': 'once | always | inherit',
      'speak-numeral': 'digits | continuous | inherit',
      'speak-punctuation': 'code | none | inherit',
      'speech-rate': 1,
      'src': 1,
      'stress': 1,
      'string-set': 1,

      'table-layout': 'auto | fixed | inherit',
      'tab-size': '<integer> | <length>',
      'target': 1,
      'target-name': 1,
      'target-new': 1,
      'target-position': 1,
      'text-align': 'left | right | center | justify | inherit',
      'text-align-last': 1,
      'text-decoration': 1,
      'text-emphasis': 1,
      'text-height': 1,
      'text-indent': '<length> | <percentage> | inherit',
      'text-justify': 'auto | none | inter-word | inter-ideograph | inter-cluster | distribute | kashida',
      'text-outline': 1,
      'text-overflow': 1,
      'text-rendering': 'auto | optimizeSpeed | optimizeLegibility | geometricPrecision | inherit',
      'text-shadow': 1,
      'text-transform': 'capitalize | uppercase | lowercase | none | inherit',
      'text-wrap': 'normal | none | avoid',
      'top': '<margin-width> | inherit',
      '-ms-touch-action': 'auto | none | pan-x | pan-y',
      'touch-action': 'auto | none | pan-x | pan-y',
      'transform': 1,
      'transform-origin': 1,
      'transform-style': 1,
      'transition': 1,
      'transition-delay': 1,
      'transition-duration': 1,
      'transition-property': 1,
      'transition-timing-function': 1,
      'unicode-bidi': 'normal | embed | isolate | bidi-override | isolate-override | plaintext | inherit',
      'user-modify': 'read-only | read-write | write-only | inherit',
      'user-select': 'none | text | toggle | element | elements | all | inherit',
      'user-zoom': 'zoom | fixed',
      'vertical-align': 'auto | use-script | baseline | sub | super | top | text-top | central | middle | bottom | text-bottom | <percentage> | <length>',
      'visibility': 'visible | hidden | collapse | inherit',
      'voice-balance': 1,
      'voice-duration': 1,
      'voice-family': 1,
      'voice-pitch': 1,
      'voice-pitch-range': 1,
      'voice-rate': 1,
      'voice-stress': 1,
      'voice-volume': 1,
      'volume': 1,
      'white-space': 'normal | pre | nowrap | pre-wrap | pre-line | inherit | -pre-wrap | -o-pre-wrap | -moz-pre-wrap | -hp-pre-wrap', // http://perishablepress.com/wrapping-content/
      'white-space-collapse': 1,
      'widows': '<integer> | inherit',
      'width': '<length> | <percentage> | <content-sizing> | auto | inherit',
      'word-break': 'normal | keep-all | break-all',
      'word-spacing': '<length> | normal | inherit',
      'word-wrap': 'normal | break-word',
      'writing-mode': 'horizontal-tb | vertical-rl | vertical-lr | lr-tb | rl-tb | tb-rl | bt-rl | tb-lr | bt-lr | lr-bt | rl-bt | lr | rl | tb | inherit',
      'z-index': '<integer> | auto | inherit',
      'zoom': '<number> | <percentage> | normal'
    }
    function PropertyName(text, hack, line, col) {
      SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_NAME_TYPE)
      this.hack = hack
    }

    PropertyName.prototype = new SyntaxUnit()
    PropertyName.prototype.constructor = PropertyName
    PropertyName.prototype.toString = function() {
      return (this.hack ? this.hack : '') + this.text
    }
    function PropertyValue(parts, line, col) {
      SyntaxUnit.call(this, parts.join(' '), line, col, Parser.PROPERTY_VALUE_TYPE)
      this.parts = parts
    }

    PropertyValue.prototype = new SyntaxUnit()
    PropertyValue.prototype.constructor = PropertyValue
    function PropertyValueIterator(value) {
      this._i = 0
      this._parts = value.parts
      this._marks = []
      this.value = value
    }
    PropertyValueIterator.prototype.count = function() {
      return this._parts.length
    }
    PropertyValueIterator.prototype.isFirst = function() {
      return this._i === 0
    }
    PropertyValueIterator.prototype.hasNext = function() {
      return (this._i < this._parts.length)
    }
    PropertyValueIterator.prototype.mark = function() {
      this._marks.push(this._i)
    }
    PropertyValueIterator.prototype.peek = function(count) {
      return this.hasNext() ? this._parts[this._i + (count || 0)] : null
    }
    PropertyValueIterator.prototype.next = function() {
      return this.hasNext() ? this._parts[this._i++] : null
    }
    PropertyValueIterator.prototype.previous = function() {
      return this._i > 0 ? this._parts[--this._i] : null
    }
    PropertyValueIterator.prototype.restore = function() {
      if (this._marks.length) {
        this._i = this._marks.pop()
      }
    }
    function PropertyValuePart(text, line, col) {
      SyntaxUnit.call(this, text, line, col, Parser.PROPERTY_VALUE_PART_TYPE)
      this.type = 'unknown'
      var temp
      if (/^([+\-]?[\d\.]+)([a-z]+)$/i.test(text)) { // dimension
        this.type = 'dimension'
        this.value = +RegExp.$1
        this.units = RegExp.$2
        switch (this.units.toLowerCase()) {
          case 'em':
          case 'rem':
          case 'ex':
          case 'px':
          case 'cm':
          case 'mm':
          case 'in':
          case 'pt':
          case 'pc':
          case 'ch':
          case 'vh':
          case 'vw':
          case 'vmax':
          case 'vmin':
            this.type = 'length'
            break

          case 'deg':
          case 'rad':
          case 'grad':
            this.type = 'angle'
            break

          case 'ms':
          case 's':
            this.type = 'time'
            break

          case 'hz':
          case 'khz':
            this.type = 'frequency'
            break

          case 'dpi':
          case 'dpcm':
            this.type = 'resolution'
            break
        }
      } else if (/^([+\-]?[\d\.]+)%$/i.test(text)) { // percentage
        this.type = 'percentage'
        this.value = +RegExp.$1
      } else if (/^([+\-]?\d+)$/i.test(text)) { // integer
        this.type = 'integer'
        this.value = +RegExp.$1
      } else if (/^([+\-]?[\d\.]+)$/i.test(text)) { // number
        this.type = 'number'
        this.value = +RegExp.$1
      } else if (/^#([a-f0-9]{3,6})/i.test(text)) { // hexcolor
        this.type = 'color'
        temp = RegExp.$1
        if (temp.length == 3) {
          this.red = parseInt(temp.charAt(0) + temp.charAt(0), 16)
          this.green = parseInt(temp.charAt(1) + temp.charAt(1), 16)
          this.blue = parseInt(temp.charAt(2) + temp.charAt(2), 16)
        } else {
          this.red = parseInt(temp.substring(0, 2), 16)
          this.green = parseInt(temp.substring(2, 4), 16)
          this.blue = parseInt(temp.substring(4, 6), 16)
        }
      } else if (/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i.test(text)) { // rgb() color with absolute numbers
        this.type = 'color'
        this.red = +RegExp.$1
        this.green = +RegExp.$2
        this.blue = +RegExp.$3
      } else if (/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)) { // rgb() color with percentages
        this.type = 'color'
        this.red = +RegExp.$1 * 255 / 100
        this.green = +RegExp.$2 * 255 / 100
        this.blue = +RegExp.$3 * 255 / 100
      } else if (/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/i.test(text)) { // rgba() color with absolute numbers
        this.type = 'color'
        this.red = +RegExp.$1
        this.green = +RegExp.$2
        this.blue = +RegExp.$3
        this.alpha = +RegExp.$4
      } else if (/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)) { // rgba() color with percentages
        this.type = 'color'
        this.red = +RegExp.$1 * 255 / 100
        this.green = +RegExp.$2 * 255 / 100
        this.blue = +RegExp.$3 * 255 / 100
        this.alpha = +RegExp.$4
      } else if (/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/i.test(text)) { // hsl()
        this.type = 'color'
        this.hue = +RegExp.$1
        this.saturation = +RegExp.$2 / 100
        this.lightness = +RegExp.$3 / 100
      } else if (/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*([\d\.]+)\s*\)/i.test(text)) { // hsla() color with percentages
        this.type = 'color'
        this.hue = +RegExp.$1
        this.saturation = +RegExp.$2 / 100
        this.lightness = +RegExp.$3 / 100
        this.alpha = +RegExp.$4
      } else if (/^url\(["']?([^\)"']+)["']?\)/i.test(text)) { // URI
        this.type = 'uri'
        this.uri = RegExp.$1
      } else if (/^([^\(]+)\(/i.test(text)) {
        this.type = 'function'
        this.name = RegExp.$1
        this.value = text
      } else if (/^["'][^"']*["']/.test(text)) { // string
        this.type = 'string'
        this.value = eval(text)
      } else if (Colors[text.toLowerCase()]) { // named color
        this.type = 'color'
        temp = Colors[text.toLowerCase()].substring(1)
        this.red = parseInt(temp.substring(0, 2), 16)
        this.green = parseInt(temp.substring(2, 4), 16)
        this.blue = parseInt(temp.substring(4, 6), 16)
      } else if (/^[\,\/]$/.test(text)) {
        this.type = 'operator'
        this.value = text
      } else if (/^[a-z\-_\u0080-\uFFFF][a-z0-9\-_\u0080-\uFFFF]*$/i.test(text)) {
        this.type = 'identifier'
        this.value = text
      }
    }

    PropertyValuePart.prototype = new SyntaxUnit()
    PropertyValuePart.prototype.constructor = PropertyValuePart
    PropertyValuePart.fromToken = function(token) {
      return new PropertyValuePart(token.value, token.startLine, token.startCol)
    }
    var Pseudos = {
      ':first-letter': 1,
      ':first-line': 1,
      ':before': 1,
      ':after': 1
    }

    Pseudos.ELEMENT = 1
    Pseudos.CLASS = 2

    Pseudos.isElement = function(pseudo) {
      return pseudo.indexOf('::') === 0 || Pseudos[pseudo.toLowerCase()] == Pseudos.ELEMENT
    }
    function Selector(parts, line, col) {
      SyntaxUnit.call(this, parts.join(' '), line, col, Parser.SELECTOR_TYPE)
      this.parts = parts
      this.specificity = Specificity.calculate(this)
    }

    Selector.prototype = new SyntaxUnit()
    Selector.prototype.constructor = Selector
    function SelectorPart(elementName, modifiers, text, line, col) {
      SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_PART_TYPE)
      this.elementName = elementName
      this.modifiers = modifiers
    }

    SelectorPart.prototype = new SyntaxUnit()
    SelectorPart.prototype.constructor = SelectorPart
    function SelectorSubPart(text, type, line, col) {
      SyntaxUnit.call(this, text, line, col, Parser.SELECTOR_SUB_PART_TYPE)
      this.type = type
      this.args = []
    }

    SelectorSubPart.prototype = new SyntaxUnit()
    SelectorSubPart.prototype.constructor = SelectorSubPart
    function Specificity(a, b, c, d) {
      this.a = a
      this.b = b
      this.c = c
      this.d = d
    }

    Specificity.prototype = {
      constructor: Specificity,
      compare: function(other) {
        var comps = ['a', 'b', 'c', 'd'],
          i, len

        for (i = 0, len = comps.length; i < len; i++) {
          if (this[comps[i]] < other[comps[i]]) {
            return -1
          } else if (this[comps[i]] > other[comps[i]]) {
            return 1
          }
        }

        return 0
      },
      valueOf: function() {
        return (this.a * 1000) + (this.b * 100) + (this.c * 10) + this.d
      },
      toString: function() {
        return this.a + ',' + this.b + ',' + this.c + ',' + this.d
      }

    }
    Specificity.calculate = function(selector) {
      var i, len,
        part,
        b = 0, c = 0, d = 0

      function updateValues(part) {
        var i, j, len, num,
          elementName = part.elementName ? part.elementName.text : '',
          modifier

        if (elementName && elementName.charAt(elementName.length - 1) != '*') {
          d++
        }

        for (i = 0, len = part.modifiers.length; i < len; i++) {
          modifier = part.modifiers[i]
          switch (modifier.type) {
            case 'class':
            case 'attribute':
              c++
              break

            case 'id':
              b++
              break

            case 'pseudo':
              if (Pseudos.isElement(modifier.text)) {
                d++
              } else {
                c++
              }
              break

            case 'not':
              for (j = 0, num = modifier.args.length; j < num; j++) {
                updateValues(modifier.args[j])
              }
          }
        }
      }

      for (i = 0, len = selector.parts.length; i < len; i++) {
        part = selector.parts[i]

        if (part instanceof SelectorPart) {
          updateValues(part)
        }
      }

      return new Specificity(0, b, c, d)
    }

    var h = /^[0-9a-fA-F]$/,
      nonascii = /^[\u0080-\uFFFF]$/,
      nl = /\n|\r\n|\r|\f/

    function isHexDigit(c) {
      return c !== null && h.test(c)
    }

    function isDigit(c) {
      return c !== null && /\d/.test(c)
    }

    function isWhitespace(c) {
      return c !== null && /\s/.test(c)
    }

    function isNewLine(c) {
      return c !== null && nl.test(c)
    }

    function isNameStart(c) {
      return c !== null && (/[a-z_\u0080-\uFFFF\\]/i.test(c))
    }

    function isNameChar(c) {
      return c !== null && (isNameStart(c) || /[0-9\-\\]/.test(c))
    }

    function isIdentStart(c) {
      return c !== null && (isNameStart(c) || /\-\\/.test(c))
    }

    function mix(receiver, supplier) {
      for (var prop in supplier) {
        if (supplier.hasOwnProperty(prop)) {
          receiver[prop] = supplier[prop]
        }
      }
      return receiver
    }
    function TokenStream(input) {
      TokenStreamBase.call(this, input, Tokens)
    }

    TokenStream.prototype = mix(new TokenStreamBase(), {
      _getToken: function(channel) {
        var c,
          reader = this._reader,
          token = null,
          startLine = reader.getLine(),
          startCol = reader.getCol()

        c = reader.read()

        while (c) {
          switch (c) {
            case '/':

              if (reader.peek() == '*') {
                token = this.commentToken(c, startLine, startCol)
              } else {
                token = this.charToken(c, startLine, startCol)
              }
              break
            case '|':
            case '~':
            case '^':
            case '$':
            case '*':
              if (reader.peek() == '=') {
                token = this.comparisonToken(c, startLine, startCol)
              } else {
                token = this.charToken(c, startLine, startCol)
              }
              break
            case '"':
            case "'":
              token = this.stringToken(c, startLine, startCol)
              break
            case '#':
              if (isNameChar(reader.peek())) {
                token = this.hashToken(c, startLine, startCol)
              } else {
                token = this.charToken(c, startLine, startCol)
              }
              break
            case '.':
              if (isDigit(reader.peek())) {
                token = this.numberToken(c, startLine, startCol)
              } else {
                token = this.charToken(c, startLine, startCol)
              }
              break
            case '-':
              if (reader.peek() == '-') { // could be closing HTML-style comment
                token = this.htmlCommentEndToken(c, startLine, startCol)
              } else if (isNameStart(reader.peek())) {
                token = this.identOrFunctionToken(c, startLine, startCol)
              } else {
                token = this.charToken(c, startLine, startCol)
              }
              break
            case '!':
              token = this.importantToken(c, startLine, startCol)
              break
            case '@':
              token = this.atRuleToken(c, startLine, startCol)
              break
            case ':':
              token = this.notToken(c, startLine, startCol)
              break
            case '<':
              token = this.htmlCommentStartToken(c, startLine, startCol)
              break
            case 'U':
            case 'u':
              if (reader.peek() == '+') {
                token = this.unicodeRangeToken(c, startLine, startCol)
                break
              }
            default:
              if (isDigit(c)) {
                token = this.numberToken(c, startLine, startCol)
              } else
              if (isWhitespace(c)) {
                token = this.whitespaceToken(c, startLine, startCol)
              } else
              if (isIdentStart(c)) {
                token = this.identOrFunctionToken(c, startLine, startCol)
              } else {
                token = this.charToken(c, startLine, startCol)
              }
          }
          break
        }

        if (!token && c === null) {
          token = this.createToken(Tokens.EOF, null, startLine, startCol)
        }

        return token
      },
      createToken: function(tt, value, startLine, startCol, options) {
        var reader = this._reader
        options = options || {}

        return {
          value: value,
          type: tt,
          channel: options.channel,
          endChar: options.endChar,
          hide: options.hide || false,
          startLine: startLine,
          startCol: startCol,
          endLine: reader.getLine(),
          endCol: reader.getCol()
        }
      },
      atRuleToken: function(first, startLine, startCol) {
        var rule = first,
          reader = this._reader,
          tt = Tokens.CHAR,
          valid = false,
          ident,
          c
        reader.mark()
        ident = this.readName()
        rule = first + ident
        tt = Tokens.type(rule.toLowerCase())
        if (tt == Tokens.CHAR || tt == Tokens.UNKNOWN) {
          if (rule.length > 1) {
            tt = Tokens.UNKNOWN_SYM
          } else {
            tt = Tokens.CHAR
            rule = first
            reader.reset()
          }
        }

        return this.createToken(tt, rule, startLine, startCol)
      },
      charToken: function(c, startLine, startCol) {
        var tt = Tokens.type(c)
        var opts = {}

        if (tt == -1) {
          tt = Tokens.CHAR
        } else {
          opts.endChar = Tokens[tt].endChar
        }

        return this.createToken(tt, c, startLine, startCol, opts)
      },
      commentToken: function(first, startLine, startCol) {
        var reader = this._reader,
          comment = this.readComment(first)

        return this.createToken(Tokens.COMMENT, comment, startLine, startCol)
      },
      comparisonToken: function(c, startLine, startCol) {
        var reader = this._reader,
          comparison = c + reader.read(),
          tt = Tokens.type(comparison) || Tokens.CHAR

        return this.createToken(tt, comparison, startLine, startCol)
      },
      hashToken: function(first, startLine, startCol) {
        var reader = this._reader,
          name = this.readName(first)

        return this.createToken(Tokens.HASH, name, startLine, startCol)
      },
      htmlCommentStartToken: function(first, startLine, startCol) {
        var reader = this._reader,
          text = first

        reader.mark()
        text += reader.readCount(3)

        if (text == '<!--') {
          return this.createToken(Tokens.CDO, text, startLine, startCol)
        } else {
          reader.reset()
          return this.charToken(first, startLine, startCol)
        }
      },
      htmlCommentEndToken: function(first, startLine, startCol) {
        var reader = this._reader,
          text = first

        reader.mark()
        text += reader.readCount(2)

        if (text == '-->') {
          return this.createToken(Tokens.CDC, text, startLine, startCol)
        } else {
          reader.reset()
          return this.charToken(first, startLine, startCol)
        }
      },
      identOrFunctionToken: function(first, startLine, startCol) {
        var reader = this._reader,
          ident = this.readName(first),
          tt = Tokens.IDENT
        if (reader.peek() == '(') {
          ident += reader.read()
          if (ident.toLowerCase() == 'url(') {
            tt = Tokens.URI
            ident = this.readURI(ident)
            if (ident.toLowerCase() == 'url(') {
              tt = Tokens.FUNCTION
            }
          } else {
            tt = Tokens.FUNCTION
          }
        } else if (reader.peek() == ':') { // might be an IE function
          if (ident.toLowerCase() == 'progid') {
            ident += reader.readTo('(')
            tt = Tokens.IE_FUNCTION
          }
        }

        return this.createToken(tt, ident, startLine, startCol)
      },
      importantToken: function(first, startLine, startCol) {
        var reader = this._reader,
          important = first,
          tt = Tokens.CHAR,
          temp,
          c

        reader.mark()
        c = reader.read()

        while (c) {
          if (c == '/') {
            if (reader.peek() != '*') {
              break
            } else {
              temp = this.readComment(c)
              if (temp === '') { // broken!
                break
              }
            }
          } else if (isWhitespace(c)) {
            important += c + this.readWhitespace()
          } else if (/i/i.test(c)) {
            temp = reader.readCount(8)
            if (/mportant/i.test(temp)) {
              important += c + temp
              tt = Tokens.IMPORTANT_SYM
            }
            break // we're done
          } else {
            break
          }

          c = reader.read()
        }

        if (tt == Tokens.CHAR) {
          reader.reset()
          return this.charToken(first, startLine, startCol)
        } else {
          return this.createToken(tt, important, startLine, startCol)
        }
      },
      notToken: function(first, startLine, startCol) {
        var reader = this._reader,
          text = first

        reader.mark()
        text += reader.readCount(4)

        if (text.toLowerCase() == ':not(') {
          return this.createToken(Tokens.NOT, text, startLine, startCol)
        } else {
          reader.reset()
          return this.charToken(first, startLine, startCol)
        }
      },
      numberToken: function(first, startLine, startCol) {
        var reader = this._reader,
          value = this.readNumber(first),
          ident,
          tt = Tokens.NUMBER,
          c = reader.peek()

        if (isIdentStart(c)) {
          ident = this.readName(reader.read())
          value += ident

          if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vmax$|^vmin$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)) {
            tt = Tokens.LENGTH
          } else if (/^deg|^rad$|^grad$/i.test(ident)) {
            tt = Tokens.ANGLE
          } else if (/^ms$|^s$/i.test(ident)) {
            tt = Tokens.TIME
          } else if (/^hz$|^khz$/i.test(ident)) {
            tt = Tokens.FREQ
          } else if (/^dpi$|^dpcm$/i.test(ident)) {
            tt = Tokens.RESOLUTION
          } else {
            tt = Tokens.DIMENSION
          }
        } else if (c == '%') {
          value += reader.read()
          tt = Tokens.PERCENTAGE
        }

        return this.createToken(tt, value, startLine, startCol)
      },
      stringToken: function(first, startLine, startCol) {
        var delim = first,
          string = first,
          reader = this._reader,
          prev = first,
          tt = Tokens.STRING,
          c = reader.read()

        while (c) {
          string += c
          if (c == delim && prev != '\\') {
            break
          }
          if (isNewLine(reader.peek()) && c != '\\') {
            tt = Tokens.INVALID
            break
          }
          prev = c
          c = reader.read()
        }
        if (c === null) {
          tt = Tokens.INVALID
        }

        return this.createToken(tt, string, startLine, startCol)
      },

      unicodeRangeToken: function(first, startLine, startCol) {
        var reader = this._reader,
          value = first,
          temp,
          tt = Tokens.CHAR
        if (reader.peek() == '+') {
          reader.mark()
          value += reader.read()
          value += this.readUnicodeRangePart(true)
          if (value.length == 2) {
            reader.reset()
          } else {
            tt = Tokens.UNICODE_RANGE
            if (value.indexOf('?') == -1) {
              if (reader.peek() == '-') {
                reader.mark()
                temp = reader.read()
                temp += this.readUnicodeRangePart(false)
                if (temp.length == 1) {
                  reader.reset()
                } else {
                  value += temp
                }
              }
            }
          }
        }

        return this.createToken(tt, value, startLine, startCol)
      },
      whitespaceToken: function(first, startLine, startCol) {
        var reader = this._reader,
          value = first + this.readWhitespace()
        return this.createToken(Tokens.S, value, startLine, startCol)
      },
      readUnicodeRangePart: function(allowQuestionMark) {
        var reader = this._reader,
          part = '',
          c = reader.peek()
        while (isHexDigit(c) && part.length < 6) {
          reader.read()
          part += c
          c = reader.peek()
        }
        if (allowQuestionMark) {
          while (c == '?' && part.length < 6) {
            reader.read()
            part += c
            c = reader.peek()
          }
        }
        return part
      },

      readWhitespace: function() {
        var reader = this._reader,
          whitespace = '',
          c = reader.peek()

        while (isWhitespace(c)) {
          reader.read()
          whitespace += c
          c = reader.peek()
        }

        return whitespace
      },
      readNumber: function(first) {
        var reader = this._reader,
          number = first,
          hasDot = (first == '.'),
          c = reader.peek()

        while (c) {
          if (isDigit(c)) {
            number += reader.read()
          } else if (c == '.') {
            if (hasDot) {
              break
            } else {
              hasDot = true
              number += reader.read()
            }
          } else {
            break
          }

          c = reader.peek()
        }

        return number
      },
      readString: function() {
        var reader = this._reader,
          delim = reader.read(),
          string = delim,
          prev = delim,
          c = reader.peek()

        while (c) {
          c = reader.read()
          string += c
          if (c == delim && prev != '\\') {
            break
          }
          if (isNewLine(reader.peek()) && c != '\\') {
            string = ''
            break
          }
          prev = c
          c = reader.peek()
        }
        if (c === null) {
          string = ''
        }

        return string
      },
      readURI: function(first) {
        var reader = this._reader,
          uri = first,
          inner = '',
          c = reader.peek()

        reader.mark()
        while (c && isWhitespace(c)) {
          reader.read()
          c = reader.peek()
        }
        if (c == "'" || c == '"') {
          inner = this.readString()
        } else {
          inner = this.readURL()
        }

        c = reader.peek()
        while (c && isWhitespace(c)) {
          reader.read()
          c = reader.peek()
        }
        if (inner === '' || c != ')') {
          uri = first
          reader.reset()
        } else {
          uri += inner + reader.read()
        }

        return uri
      },
      readURL: function() {
        var reader = this._reader,
          url = '',
          c = reader.peek()
        while (/^[!#$%&\\*-~]$/.test(c)) {
          url += reader.read()
          c = reader.peek()
        }

        return url
      },
      readName: function(first) {
        var reader = this._reader,
          ident = first || '',
          c = reader.peek()

        while (true) {
          if (c == '\\') {
            ident += this.readEscape(reader.read())
            c = reader.peek()
          } else if (c && isNameChar(c)) {
            ident += reader.read()
            c = reader.peek()
          } else {
            break
          }
        }

        return ident
      },

      readEscape: function(first) {
        var reader = this._reader,
          cssEscape = first || '',
          i = 0,
          c = reader.peek()

        if (isHexDigit(c)) {
          do {
            cssEscape += reader.read()
            c = reader.peek()
          } while (c && isHexDigit(c) && ++i < 6)
        }

        if (cssEscape.length == 3 && /\s/.test(c) ||
            cssEscape.length == 7 || cssEscape.length == 1) {
          reader.read()
        } else {
          c = ''
        }

        return cssEscape + c
      },

      readComment: function(first) {
        var reader = this._reader,
          comment = first || '',
          c = reader.read()

        if (c == '*') {
          while (c) {
            comment += c
            if (comment.length > 2 && c == '*' && reader.peek() == '/') {
              comment += reader.read()
              break
            }

            c = reader.read()
          }

          return comment
        } else {
          return ''
        }
      }
    })

    var Tokens = [
      { name: 'CDO' },
      { name: 'CDC' },
      { name: 'S', whitespace: true/*, channel: "ws"*/ },
      { name: 'COMMENT', comment: true, hide: true, channel: 'comment' },
      { name: 'INCLUDES', text: '~=' },
      { name: 'DASHMATCH', text: '|=' },
      { name: 'PREFIXMATCH', text: '^=' },
      { name: 'SUFFIXMATCH', text: '$=' },
      { name: 'SUBSTRINGMATCH', text: '*=' },
      { name: 'STRING' },
      { name: 'IDENT' },
      { name: 'HASH' },
      { name: 'IMPORT_SYM', text: '@import' },
      { name: 'PAGE_SYM', text: '@page' },
      { name: 'MEDIA_SYM', text: '@media' },
      { name: 'FONT_FACE_SYM', text: '@font-face' },
      { name: 'CHARSET_SYM', text: '@charset' },
      { name: 'NAMESPACE_SYM', text: '@namespace' },
      { name: 'VIEWPORT_SYM', text: ['@viewport', '@-ms-viewport'] },
      { name: 'UNKNOWN_SYM' },
      { name: 'KEYFRAMES_SYM', text: ['@keyframes', '@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes'] },
      { name: 'IMPORTANT_SYM' },
      { name: 'LENGTH' },
      { name: 'ANGLE' },
      { name: 'TIME' },
      { name: 'FREQ' },
      { name: 'DIMENSION' },
      { name: 'PERCENTAGE' },
      { name: 'NUMBER' },
      { name: 'URI' },
      { name: 'FUNCTION' },
      { name: 'UNICODE_RANGE' },
      { name: 'INVALID' },
      { name: 'PLUS', text: '+' },
      { name: 'GREATER', text: '>' },
      { name: 'COMMA', text: ',' },
      { name: 'TILDE', text: '~' },
      { name: 'NOT' },
      { name: 'TOPLEFTCORNER_SYM', text: '@top-left-corner' },
      { name: 'TOPLEFT_SYM', text: '@top-left' },
      { name: 'TOPCENTER_SYM', text: '@top-center' },
      { name: 'TOPRIGHT_SYM', text: '@top-right' },
      { name: 'TOPRIGHTCORNER_SYM', text: '@top-right-corner' },
      { name: 'BOTTOMLEFTCORNER_SYM', text: '@bottom-left-corner' },
      { name: 'BOTTOMLEFT_SYM', text: '@bottom-left' },
      { name: 'BOTTOMCENTER_SYM', text: '@bottom-center' },
      { name: 'BOTTOMRIGHT_SYM', text: '@bottom-right' },
      { name: 'BOTTOMRIGHTCORNER_SYM', text: '@bottom-right-corner' },
      { name: 'LEFTTOP_SYM', text: '@left-top' },
      { name: 'LEFTMIDDLE_SYM', text: '@left-middle' },
      { name: 'LEFTBOTTOM_SYM', text: '@left-bottom' },
      { name: 'RIGHTTOP_SYM', text: '@right-top' },
      { name: 'RIGHTMIDDLE_SYM', text: '@right-middle' },
      { name: 'RIGHTBOTTOM_SYM', text: '@right-bottom' },
      { name: 'RESOLUTION', state: 'media' },
      { name: 'IE_FUNCTION' },
      { name: 'CHAR' },
      {
        name: 'PIPE',
        text: '|'
      },
      {
        name: 'SLASH',
        text: '/'
      },
      {
        name: 'MINUS',
        text: '-'
      },
      {
        name: 'STAR',
        text: '*'
      },

      {
        name: 'LBRACE',
        endChar: '}',
        text: '{'
      },
      {
        name: 'RBRACE',
        text: '}'
      },
      {
        name: 'LBRACKET',
        endChar: ']',
        text: '['
      },
      {
        name: 'RBRACKET',
        text: ']'
      },
      {
        name: 'EQUALS',
        text: '='
      },
      {
        name: 'COLON',
        text: ':'
      },
      {
        name: 'SEMICOLON',
        text: ';'
      },

      {
        name: 'LPAREN',
        endChar: ')',
        text: '('
      },
      {
        name: 'RPAREN',
        text: ')'
      },
      {
        name: 'DOT',
        text: '.'
      }
    ];

    (function() {
      var nameMap = [],
        typeMap = {}

      Tokens.UNKNOWN = -1
      Tokens.unshift({ name: 'EOF' })
      for (var i = 0, len = Tokens.length; i < len; i++) {
        nameMap.push(Tokens[i].name)
        Tokens[Tokens[i].name] = i
        if (Tokens[i].text) {
          if (Tokens[i].text instanceof Array) {
            for (var j = 0; j < Tokens[i].text.length; j++) {
              typeMap[Tokens[i].text[j]] = i
            }
          } else {
            typeMap[Tokens[i].text] = i
          }
        }
      }

      Tokens.name = function(tt) {
        return nameMap[tt]
      }

      Tokens.type = function(c) {
        return typeMap[c] || -1
      }
    })()
    var Validation = {

      validate: function(property, value) {
        var name = property.toString().toLowerCase(),
          parts = value.parts,
          expression = new PropertyValueIterator(value),
          spec = Properties[name],
          part,
          valid,
          j, count,
          msg,
          types,
          last,
          literals,
          max, multi, group

        if (!spec) {
          if (name.indexOf('-') !== 0) { // vendor prefixed are ok
            throw new ValidationError("Unknown property '" + property + "'.", property.line, property.col)
          }
        } else if (typeof spec !== 'number') {
          if (typeof spec === 'string') {
            if (spec.indexOf('||') > -1) {
              this.groupProperty(spec, expression)
            } else {
              this.singleProperty(spec, expression, 1)
            }
          } else if (spec.multi) {
            this.multiProperty(spec.multi, expression, spec.comma, spec.max || Infinity)
          } else if (typeof spec === 'function') {
            spec(expression)
          }
        }
      },

      singleProperty: function(types, expression, max, partial) {
        var result = false,
          value = expression.value,
          count = 0,
          part

        while (expression.hasNext() && count < max) {
          result = ValidationTypes.isAny(expression, types)
          if (!result) {
            break
          }
          count++
        }

        if (!result) {
          if (expression.hasNext() && !expression.isFirst()) {
            part = expression.peek()
            throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
          } else {
            throw new ValidationError('Expected (' + types + ") but found '" + value + "'.", value.line, value.col)
          }
        } else if (expression.hasNext()) {
          part = expression.next()
          throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
        }
      },

      multiProperty: function(types, expression, comma, max) {
        var result = false,
          value = expression.value,
          count = 0,
          sep = false,
          part

        while (expression.hasNext() && !result && count < max) {
          if (ValidationTypes.isAny(expression, types)) {
            count++
            if (!expression.hasNext()) {
              result = true
            } else if (comma) {
              if (expression.peek() == ',') {
                part = expression.next()
              } else {
                break
              }
            }
          } else {
            break
          }
        }

        if (!result) {
          if (expression.hasNext() && !expression.isFirst()) {
            part = expression.peek()
            throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
          } else {
            part = expression.previous()
            if (comma && part == ',') {
              throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
            } else {
              throw new ValidationError('Expected (' + types + ") but found '" + value + "'.", value.line, value.col)
            }
          }
        } else if (expression.hasNext()) {
          part = expression.next()
          throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
        }
      },

      groupProperty: function(types, expression, comma) {
        var result = false,
          value = expression.value,
          typeCount = types.split('||').length,
          groups = { count: 0 },
          partial = false,
          name,
          part

        while (expression.hasNext() && !result) {
          name = ValidationTypes.isAnyOfGroup(expression, types)
          if (name) {
            if (groups[name]) {
              break
            } else {
              groups[name] = 1
              groups.count++
              partial = true

              if (groups.count == typeCount || !expression.hasNext()) {
                result = true
              }
            }
          } else {
            break
          }
        }

        if (!result) {
          if (partial && expression.hasNext()) {
            part = expression.peek()
            throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
          } else {
            throw new ValidationError('Expected (' + types + ") but found '" + value + "'.", value.line, value.col)
          }
        } else if (expression.hasNext()) {
          part = expression.next()
          throw new ValidationError("Expected end of value but found '" + part + "'.", part.line, part.col)
        }
      }

    }
    function ValidationError(message, line, col) {
      this.col = col
      this.line = line
      this.message = message
    }
    ValidationError.prototype = new Error()
    var ValidationTypes = {

      isLiteral: function(part, literals) {
        var text = part.text.toString().toLowerCase(),
          args = literals.split(' | '),
          i, len, found = false

        for (i = 0, len = args.length; i < len && !found; i++) {
          if (text == args[i].toLowerCase()) {
            found = true
          }
        }

        return found
      },

      isSimple: function(type) {
        return !!this.simple[type]
      },

      isComplex: function(type) {
        return !!this.complex[type]
      },
      isAny: function(expression, types) {
        var args = types.split(' | '),
          i, len, found = false

        for (i = 0, len = args.length; i < len && !found && expression.hasNext(); i++) {
          found = this.isType(expression, args[i])
        }

        return found
      },
      isAnyOfGroup: function(expression, types) {
        var args = types.split(' || '),
          i, len, found = false

        for (i = 0, len = args.length; i < len && !found; i++) {
          found = this.isType(expression, args[i])
        }

        return found ? args[i - 1] : false
      },
      isType: function(expression, type) {
        var part = expression.peek(),
          result = false

        if (type.charAt(0) != '<') {
          result = this.isLiteral(part, type)
          if (result) {
            expression.next()
          }
        } else if (this.simple[type]) {
          result = this.simple[type](part)
          if (result) {
            expression.next()
          }
        } else {
          result = this.complex[type](expression)
        }

        return result
      },

      simple: {

        '<absolute-size>': function(part) {
          return ValidationTypes.isLiteral(part, 'xx-small | x-small | small | medium | large | x-large | xx-large')
        },

        '<attachment>': function(part) {
          return ValidationTypes.isLiteral(part, 'scroll | fixed | local')
        },

        '<attr>': function(part) {
          return part.type == 'function' && part.name == 'attr'
        },

        '<bg-image>': function(part) {
          return this['<image>'](part) || this['<gradient>'](part) || part == 'none'
        },

        '<gradient>': function(part) {
          return part.type == 'function' && /^(?:\-(?:ms|moz|o|webkit)\-)?(?:repeating\-)?(?:radial\-|linear\-)?gradient/i.test(part)
        },

        '<box>': function(part) {
          return ValidationTypes.isLiteral(part, 'padding-box | border-box | content-box')
        },

        '<content>': function(part) {
          return part.type == 'function' && part.name == 'content'
        },

        '<relative-size>': function(part) {
          return ValidationTypes.isLiteral(part, 'smaller | larger')
        },
        '<ident>': function(part) {
          return part.type == 'identifier'
        },

        '<length>': function(part) {
          if (part.type == 'function' && /^(?:\-(?:ms|moz|o|webkit)\-)?calc/i.test(part)) {
            return true
          } else {
            return part.type == 'length' || part.type == 'number' || part.type == 'integer' || part == '0'
          }
        },

        '<color>': function(part) {
          return part.type == 'color' || part == 'transparent'
        },

        '<number>': function(part) {
          return part.type == 'number' || this['<integer>'](part)
        },

        '<integer>': function(part) {
          return part.type == 'integer'
        },

        '<line>': function(part) {
          return part.type == 'integer'
        },

        '<angle>': function(part) {
          return part.type == 'angle'
        },

        '<uri>': function(part) {
          return part.type == 'uri'
        },

        '<image>': function(part) {
          return this['<uri>'](part)
        },

        '<percentage>': function(part) {
          return part.type == 'percentage' || part == '0'
        },

        '<border-width>': function(part) {
          return this['<length>'](part) || ValidationTypes.isLiteral(part, 'thin | medium | thick')
        },

        '<border-style>': function(part) {
          return ValidationTypes.isLiteral(part, 'none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset')
        },

        '<content-sizing>': function(part) { // http://www.w3.org/TR/css3-sizing/#width-height-keywords
          return ValidationTypes.isLiteral(part, 'fill-available | -moz-available | -webkit-fill-available | max-content | -moz-max-content | -webkit-max-content | min-content | -moz-min-content | -webkit-min-content | fit-content | -moz-fit-content | -webkit-fit-content')
        },

        '<margin-width>': function(part) {
          return this['<length>'](part) || this['<percentage>'](part) || ValidationTypes.isLiteral(part, 'auto')
        },

        '<padding-width>': function(part) {
          return this['<length>'](part) || this['<percentage>'](part)
        },

        '<shape>': function(part) {
          return part.type == 'function' && (part.name == 'rect' || part.name == 'inset-rect')
        },

        '<time>': function(part) {
          return part.type == 'time'
        },

        '<flex-grow>': function(part) {
          return this['<number>'](part)
        },

        '<flex-shrink>': function(part) {
          return this['<number>'](part)
        },

        '<width>': function(part) {
          return this['<margin-width>'](part)
        },

        '<flex-basis>': function(part) {
          return this['<width>'](part)
        },

        '<flex-direction>': function(part) {
          return ValidationTypes.isLiteral(part, 'row | row-reverse | column | column-reverse')
        },

        '<flex-wrap>': function(part) {
          return ValidationTypes.isLiteral(part, 'nowrap | wrap | wrap-reverse')
        }
      },

      complex: {

        '<bg-position>': function(expression) {
          var types = this,
            result = false,
            numeric = '<percentage> | <length>',
            xDir = 'left | right',
            yDir = 'top | bottom',
            count = 0,
            hasNext = function() {
              return expression.hasNext() && expression.peek() != ','
            }

          while (expression.peek(count) && expression.peek(count) != ',') {
            count++
          }

          if (count < 3) {
            if (ValidationTypes.isAny(expression, xDir + ' | center | ' + numeric)) {
              result = true
              ValidationTypes.isAny(expression, yDir + ' | center | ' + numeric)
            } else if (ValidationTypes.isAny(expression, yDir)) {
              result = true
              ValidationTypes.isAny(expression, xDir + ' | center')
            }
          } else {
            if (ValidationTypes.isAny(expression, xDir)) {
              if (ValidationTypes.isAny(expression, yDir)) {
                result = true
                ValidationTypes.isAny(expression, numeric)
              } else if (ValidationTypes.isAny(expression, numeric)) {
                if (ValidationTypes.isAny(expression, yDir)) {
                  result = true
                  ValidationTypes.isAny(expression, numeric)
                } else if (ValidationTypes.isAny(expression, 'center')) {
                  result = true
                }
              }
            } else if (ValidationTypes.isAny(expression, yDir)) {
              if (ValidationTypes.isAny(expression, xDir)) {
                result = true
                ValidationTypes.isAny(expression, numeric)
              } else if (ValidationTypes.isAny(expression, numeric)) {
                if (ValidationTypes.isAny(expression, xDir)) {
                  result = true
                  ValidationTypes.isAny(expression, numeric)
                } else if (ValidationTypes.isAny(expression, 'center')) {
                  result = true
                }
              }
            } else if (ValidationTypes.isAny(expression, 'center')) {
              if (ValidationTypes.isAny(expression, xDir + ' | ' + yDir)) {
                result = true
                ValidationTypes.isAny(expression, numeric)
              }
            }
          }

          return result
        },

        '<bg-size>': function(expression) {
          var types = this,
            result = false,
            numeric = '<percentage> | <length> | auto',
            part,
            i, len

          if (ValidationTypes.isAny(expression, 'cover | contain')) {
            result = true
          } else if (ValidationTypes.isAny(expression, numeric)) {
            result = true
            ValidationTypes.isAny(expression, numeric)
          }

          return result
        },

        '<repeat-style>': function(expression) {
          var result = false,
            values = 'repeat | space | round | no-repeat',
            part

          if (expression.hasNext()) {
            part = expression.next()

            if (ValidationTypes.isLiteral(part, 'repeat-x | repeat-y')) {
              result = true
            } else if (ValidationTypes.isLiteral(part, values)) {
              result = true

              if (expression.hasNext() && ValidationTypes.isLiteral(expression.peek(), values)) {
                expression.next()
              }
            }
          }

          return result
        },

        '<shadow>': function(expression) {
          var result = false,
            count = 0,
            inset = false,
            color = false,
            part

          if (expression.hasNext()) {
            if (ValidationTypes.isAny(expression, 'inset')) {
              inset = true
            }

            if (ValidationTypes.isAny(expression, '<color>')) {
              color = true
            }

            while (ValidationTypes.isAny(expression, '<length>') && count < 4) {
              count++
            }

            if (expression.hasNext()) {
              if (!color) {
                ValidationTypes.isAny(expression, '<color>')
              }

              if (!inset) {
                ValidationTypes.isAny(expression, 'inset')
              }
            }

            result = (count >= 2 && count <= 4)
          }

          return result
        },

        '<x-one-radius>': function(expression) {
          var result = false,
            simple = '<length> | <percentage> | inherit'

          if (ValidationTypes.isAny(expression, simple)) {
            result = true
            ValidationTypes.isAny(expression, simple)
          }

          return result
        },

        '<flex>': function(expression) {
          var part,
            result = false
          if (ValidationTypes.isAny(expression, 'none | inherit')) {
            result = true
          } else {
            if (ValidationTypes.isType(expression, '<flex-grow>')) {
              if (expression.peek()) {
                if (ValidationTypes.isType(expression, '<flex-shrink>')) {
                  if (expression.peek()) {
                    result = ValidationTypes.isType(expression, '<flex-basis>')
                  } else {
                    result = true
                  }
                } else if (ValidationTypes.isType(expression, '<flex-basis>')) {
                  result = expression.peek() === null
                }
              } else {
                result = true
              }
            } else if (ValidationTypes.isType(expression, '<flex-basis>')) {
              result = true
            }
          }

          if (!result) {
            part = expression.peek()
            throw new ValidationError("Expected (none | [ <flex-grow> <flex-shrink>? || <flex-basis> ]) but found '" + expression.value.text + "'.", part.line, part.col)
          }

          return result
        }
      }
    }

    parserlib.css = {
      Colors: Colors,
      Combinator: Combinator,
      Parser: Parser,
      PropertyName: PropertyName,
      PropertyValue: PropertyValue,
      PropertyValuePart: PropertyValuePart,
      MediaFeature: MediaFeature,
      MediaQuery: MediaQuery,
      Selector: Selector,
      SelectorPart: SelectorPart,
      SelectorSubPart: SelectorSubPart,
      Specificity: Specificity,
      TokenStream: TokenStream,
      Tokens: Tokens,
      ValidationError: ValidationError
    }
  })();

  (function() {
    for (var prop in parserlib) {
      exports[prop] = parserlib[prop]
    }
  })()

  function objectToString(o) {
    return Object.prototype.toString.call(o)
  }
  var util = {
    isArray: function(ar) {
      return Array.isArray(ar) || (typeof ar === 'object' && objectToString(ar) === '[object Array]')
    },
    isDate: function(d) {
      return typeof d === 'object' && objectToString(d) === '[object Date]'
    },
    isRegExp: function(re) {
      return typeof re === 'object' && objectToString(re) === '[object RegExp]'
    },
    getRegExpFlags: function(re) {
      var flags = ''
      re.global && (flags += 'g')
      re.ignoreCase && (flags += 'i')
      re.multiline && (flags += 'm')
      return flags
    }
  }

  if (typeof module === 'object') { module.exports = clone }

  function clone(parent, circular, depth, prototype) {
    var allParents = []
    var allChildren = []

    var useBuffer = typeof Buffer !== 'undefined'

    if (typeof circular === 'undefined') { circular = true }

    if (typeof depth === 'undefined') { depth = Infinity }
    function _clone(parent, depth) {
      if (parent === null) { return null }

      if (depth == 0) { return parent }

      var child
      if (typeof parent !== 'object') {
        return parent
      }

      if (util.isArray(parent)) {
        child = []
      } else if (util.isRegExp(parent)) {
        child = new RegExp(parent.source, util.getRegExpFlags(parent))
        if (parent.lastIndex) child.lastIndex = parent.lastIndex
      } else if (util.isDate(parent)) {
        child = new Date(parent.getTime())
      } else if (useBuffer && Buffer.isBuffer(parent)) {
        child = new Buffer(parent.length)
        parent.copy(child)
        return child
      } else {
        if (typeof prototype === 'undefined') child = Object.create(Object.getPrototypeOf(parent))
        else child = Object.create(prototype)
      }

      if (circular) {
        var index = allParents.indexOf(parent)

        if (index != -1) {
          return allChildren[index]
        }
        allParents.push(parent)
        allChildren.push(child)
      }

      for (var i in parent) {
        child[i] = _clone(parent[i], depth - 1)
      }

      return child
    }

    return _clone(parent, depth)
  }
  clone.clonePrototype = function(parent) {
    if (parent === null) { return null }

    var c = function() {}
    c.prototype = parent
    return new c()
  }

  var CSSLint = (function() {
    var rules = [],
      formatters = [],
      embeddedRuleset = /\/\*csslint([^\*]*)\*\//,
      api = new parserlib.util.EventTarget()

    api.version = '@VERSION@'
    api.addRule = function(rule) {
      rules.push(rule)
      rules[rule.id] = rule
    }
    api.clearRules = function() {
      rules = []
    }
    api.getRules = function() {
      return [].concat(rules).sort(function(a, b) {
        return a.id > b.id ? 1 : 0
      })
    }
    api.getRuleset = function() {
      var ruleset = {},
        i = 0,
        len = rules.length

      while (i < len) {
        ruleset[rules[i++].id] = 1 // by default, everything is a warning
      }

      return ruleset
    }
    function applyEmbeddedRuleset(text, ruleset) {
      var valueMap,
        embedded = text && text.match(embeddedRuleset),
        rules = embedded && embedded[1]

      if (rules) {
        valueMap = {
          'true': 2, // true is error
          '': 1, // blank is warning
          'false': 0, // false is ignore

          '2': 2, // explicit error
          '1': 1, // explicit warning
          '0': 0 // explicit ignore
        }

        rules.toLowerCase().split(',').forEach(function(rule) {
          var pair = rule.split(':'),
            property = pair[0] || '',
            value = pair[1] || ''

          ruleset[property.trim()] = valueMap[value.trim()]
        })
      }

      return ruleset
    }
    api.addFormatter = function(formatter) {
      formatters[formatter.id] = formatter
    }
    api.getFormatter = function(formatId) {
      return formatters[formatId]
    }
    api.format = function(results, filename, formatId, options) {
      var formatter = this.getFormatter(formatId),
        result = null

      if (formatter) {
        result = formatter.startFormat()
        result += formatter.formatResults(results, filename, options || {})
        result += formatter.endFormat()
      }

      return result
    }
    api.hasFormat = function(formatId) {
      return formatters.hasOwnProperty(formatId)
    }
    api.verify = function(text, ruleset) {
      var i = 0,
        reporter,
        lines,
        report,
        parser = new parserlib.css.Parser({ starHack: true, ieFilters: true,
          underscoreHack: true, strict: false })
      lines = text.replace(/\n\r?/g, '$split$').split('$split$')

      if (!ruleset) {
        ruleset = this.getRuleset()
      }

      if (embeddedRuleset.test(text)) {
        ruleset = clone(ruleset)
        ruleset = applyEmbeddedRuleset(text, ruleset)
      }

      reporter = new Reporter(lines, ruleset)

      ruleset.errors = 2 // always report parsing errors as errors
      for (i in ruleset) {
        if (ruleset.hasOwnProperty(i) && ruleset[i]) {
          if (rules[i]) {
            rules[i].init(parser, reporter)
          }
        }
      }
      try {
        parser.parse(text)
      } catch (ex) {
        reporter.error('Fatal error, cannot continue: ' + ex.message, ex.line, ex.col, {})
      }

      report = {
        messages: reporter.messages,
        stats: reporter.stats,
        ruleset: reporter.ruleset
      }
      report.messages.sort(function(a, b) {
        if (a.rollup && !b.rollup) {
          return 1
        } else if (!a.rollup && b.rollup) {
          return -1
        } else {
          return a.line - b.line
        }
      })

      return report
    }
    return api
  })()
  function Reporter(lines, ruleset) {
    this.messages = []
    this.stats = []
    this.lines = lines
    this.ruleset = ruleset
  }

  Reporter.prototype = {
    constructor: Reporter,
    error: function(message, line, col, rule) {
      this.messages.push({
        type: 'error',
        line: line,
        col: col,
        message: message,
        evidence: this.lines[line - 1],
        rule: rule || {}
      })
    },
    warn: function(message, line, col, rule) {
      this.report(message, line, col, rule)
    },
    report: function(message, line, col, rule) {
      this.messages.push({
        type: this.ruleset[rule.id] === 2 ? 'error' : 'warning',
        line: line,
        col: col,
        message: message,
        evidence: this.lines[line - 1],
        rule: rule
      })
    },
    info: function(message, line, col, rule) {
      this.messages.push({
        type: 'info',
        line: line,
        col: col,
        message: message,
        evidence: this.lines[line - 1],
        rule: rule
      })
    },
    rollupError: function(message, rule) {
      this.messages.push({
        type: 'error',
        rollup: true,
        message: message,
        rule: rule
      })
    },
    rollupWarn: function(message, rule) {
      this.messages.push({
        type: 'warning',
        rollup: true,
        message: message,
        rule: rule
      })
    },
    stat: function(name, value) {
      this.stats[name] = value
    }
  }
  CSSLint._Reporter = Reporter
  CSSLint.Util = {
    mix: function(receiver, supplier) {
      var prop

      for (prop in supplier) {
        if (supplier.hasOwnProperty(prop)) {
          receiver[prop] = supplier[prop]
        }
      }

      return prop
    },
    indexOf: function(values, value) {
      if (values.indexOf) {
        return values.indexOf(value)
      } else {
        for (var i = 0, len = values.length; i < len; i++) {
          if (values[i] === value) {
            return i
          }
        }
        return -1
      }
    },
    forEach: function(values, func) {
      if (values.forEach) {
        return values.forEach(func)
      } else {
        for (var i = 0, len = values.length; i < len; i++) {
          func(values[i], i, values)
        }
      }
    }
  }

  CSSLint.addRule({
    id: 'adjoining-classes',
    name: 'Disallow adjoining classes',
    desc: "Don't use adjoining classes.",
    browsers: 'IE6',
    init: function(parser, reporter) {
      var rule = this
      parser.addListener('startrule', function(event) {
        var selectors = event.selectors,
          selector,
          part,
          modifier,
          classCount,
          i, j, k

        for (i = 0; i < selectors.length; i++) {
          selector = selectors[i]
          for (j = 0; j < selector.parts.length; j++) {
            part = selector.parts[j]
            if (part.type === parser.SELECTOR_PART_TYPE) {
              classCount = 0
              for (k = 0; k < part.modifiers.length; k++) {
                modifier = part.modifiers[k]
                if (modifier.type === 'class') {
                  classCount++
                }
                if (classCount > 1) {
                  reporter.report("Don't use adjoining classes.", part.line, part.col, rule)
                }
              }
            }
          }
        }
      })
    }

  })
  CSSLint.addRule({
    id: 'box-model',
    name: 'Beware of broken box size',
    desc: "Don't use width or height when using padding or border.",
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        widthProperties = {
          border: 1,
          'border-left': 1,
          'border-right': 1,
          padding: 1,
          'padding-left': 1,
          'padding-right': 1
        },
        heightProperties = {
          border: 1,
          'border-bottom': 1,
          'border-top': 1,
          padding: 1,
          'padding-bottom': 1,
          'padding-top': 1
        },
        properties,
        boxSizing = false

      function startRule() {
        properties = {}
        boxSizing = false
      }

      function endRule() {
        var prop, value

        if (!boxSizing) {
          if (properties.height) {
            for (prop in heightProperties) {
              if (heightProperties.hasOwnProperty(prop) && properties[prop]) {
                value = properties[prop].value
                if (!(prop === 'padding' && value.parts.length === 2 && value.parts[0].value === 0)) {
                  reporter.report('Using height with ' + prop + ' can sometimes make elements larger than you expect.', properties[prop].line, properties[prop].col, rule)
                }
              }
            }
          }

          if (properties.width) {
            for (prop in widthProperties) {
              if (widthProperties.hasOwnProperty(prop) && properties[prop]) {
                value = properties[prop].value

                if (!(prop === 'padding' && value.parts.length === 2 && value.parts[1].value === 0)) {
                  reporter.report('Using width with ' + prop + ' can sometimes make elements larger than you expect.', properties[prop].line, properties[prop].col, rule)
                }
              }
            }
          }
        }
      }

      parser.addListener('startrule', startRule)
      parser.addListener('startfontface', startRule)
      parser.addListener('startpage', startRule)
      parser.addListener('startpagemargin', startRule)
      parser.addListener('startkeyframerule', startRule)

      parser.addListener('property', function(event) {
        var name = event.property.text.toLowerCase()

        if (heightProperties[name] || widthProperties[name]) {
          if (!/^0\S*$/.test(event.value) && !(name === 'border' && event.value.toString() === 'none')) {
            properties[name] = { line: event.property.line, col: event.property.col, value: event.value }
          }
        } else {
          if (/^(width|height)/i.test(name) && /^(length|percentage)/.test(event.value.parts[0].type)) {
            properties[name] = 1
          } else if (name === 'box-sizing') {
            boxSizing = true
          }
        }
      })

      parser.addListener('endrule', endRule)
      parser.addListener('endfontface', endRule)
      parser.addListener('endpage', endRule)
      parser.addListener('endpagemargin', endRule)
      parser.addListener('endkeyframerule', endRule)
    }

  })

  CSSLint.addRule({
    id: 'box-sizing',
    name: 'Disallow use of box-sizing',
    desc: "The box-sizing properties isn't supported in IE6 and IE7.",
    browsers: 'IE6, IE7',
    tags: ['Compatibility'],
    init: function(parser, reporter) {
      var rule = this

      parser.addListener('property', function(event) {
        var name = event.property.text.toLowerCase()

        if (name === 'box-sizing') {
          reporter.report("The box-sizing property isn't supported in IE6 and IE7.", event.line, event.col, rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'bulletproof-font-face',
    name: 'Use the bulletproof @font-face syntax',
    desc: "Use the bulletproof @font-face syntax to avoid 404's in old IE (http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax).",
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        fontFaceRule = false,
        firstSrc = true,
        ruleFailed = false,
        line, col
      parser.addListener('startfontface', function() {
        fontFaceRule = true
      })

      parser.addListener('property', function(event) {
        if (!fontFaceRule) {
          return
        }

        var propertyName = event.property.toString().toLowerCase(),
          value = event.value.toString()
        line = event.line
        col = event.col
        if (propertyName === 'src') {
          var regex = /^\s?url\(['"].+\.eot\?.*['"]\)\s*format\(['"]embedded-opentype['"]\).*$/i
          if (!value.match(regex) && firstSrc) {
            ruleFailed = true
            firstSrc = false
          } else if (value.match(regex) && !firstSrc) {
            ruleFailed = false
          }
        }
      })
      parser.addListener('endfontface', function() {
        fontFaceRule = false

        if (ruleFailed) {
          reporter.report("@font-face declaration doesn't follow the fontspring bulletproof syntax.", line, col, rule)
        }
      })
    }
  })

  CSSLint.addRule({
    id: 'compatible-vendor-prefixes',
    name: 'Require compatible vendor prefixes',
    desc: 'Include all compatible vendor prefixes to reach a wider range of users.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        compatiblePrefixes,
        properties,
        prop,
        variations,
        prefixed,
        i,
        len,
        inKeyFrame = false,
        arrayPush = Array.prototype.push,
        applyTo = []
      compatiblePrefixes = {
        'animation': 'webkit moz',
        'animation-delay': 'webkit moz',
        'animation-direction': 'webkit moz',
        'animation-duration': 'webkit moz',
        'animation-fill-mode': 'webkit moz',
        'animation-iteration-count': 'webkit moz',
        'animation-name': 'webkit moz',
        'animation-play-state': 'webkit moz',
        'animation-timing-function': 'webkit moz',
        'appearance': 'webkit moz',
        'border-end': 'webkit moz',
        'border-end-color': 'webkit moz',
        'border-end-style': 'webkit moz',
        'border-end-width': 'webkit moz',
        'border-image': 'webkit moz o',
        'border-radius': 'webkit',
        'border-start': 'webkit moz',
        'border-start-color': 'webkit moz',
        'border-start-style': 'webkit moz',
        'border-start-width': 'webkit moz',
        'box-align': 'webkit moz ms',
        'box-direction': 'webkit moz ms',
        'box-flex': 'webkit moz ms',
        'box-lines': 'webkit ms',
        'box-ordinal-group': 'webkit moz ms',
        'box-orient': 'webkit moz ms',
        'box-pack': 'webkit moz ms',
        'box-sizing': 'webkit moz',
        'box-shadow': 'webkit moz',
        'column-count': 'webkit moz ms',
        'column-gap': 'webkit moz ms',
        'column-rule': 'webkit moz ms',
        'column-rule-color': 'webkit moz ms',
        'column-rule-style': 'webkit moz ms',
        'column-rule-width': 'webkit moz ms',
        'column-width': 'webkit moz ms',
        'hyphens': 'epub moz',
        'line-break': 'webkit ms',
        'margin-end': 'webkit moz',
        'margin-start': 'webkit moz',
        'marquee-speed': 'webkit wap',
        'marquee-style': 'webkit wap',
        'padding-end': 'webkit moz',
        'padding-start': 'webkit moz',
        'tab-size': 'moz o',
        'text-size-adjust': 'webkit ms',
        'transform': 'webkit moz ms o',
        'transform-origin': 'webkit moz ms o',
        'transition': 'webkit moz o',
        'transition-delay': 'webkit moz o',
        'transition-duration': 'webkit moz o',
        'transition-property': 'webkit moz o',
        'transition-timing-function': 'webkit moz o',
        'user-modify': 'webkit moz',
        'user-select': 'webkit moz ms',
        'word-break': 'epub ms',
        'writing-mode': 'epub ms'
      }

      for (prop in compatiblePrefixes) {
        if (compatiblePrefixes.hasOwnProperty(prop)) {
          variations = []
          prefixed = compatiblePrefixes[prop].split(' ')
          for (i = 0, len = prefixed.length; i < len; i++) {
            variations.push('-' + prefixed[i] + '-' + prop)
          }
          compatiblePrefixes[prop] = variations
          arrayPush.apply(applyTo, variations)
        }
      }

      parser.addListener('startrule', function() {
        properties = []
      })

      parser.addListener('startkeyframes', function(event) {
        inKeyFrame = event.prefix || true
      })

      parser.addListener('endkeyframes', function() {
        inKeyFrame = false
      })

      parser.addListener('property', function(event) {
        var name = event.property
        if (CSSLint.Util.indexOf(applyTo, name.text) > -1) {
          if (!inKeyFrame || typeof inKeyFrame !== 'string' ||
                        name.text.indexOf('-' + inKeyFrame + '-') !== 0) {
            properties.push(name)
          }
        }
      })

      parser.addListener('endrule', function() {
        if (!properties.length) {
          return
        }

        var propertyGroups = {},
          i,
          len,
          name,
          prop,
          variations,
          value,
          full,
          actual,
          item,
          propertiesSpecified

        for (i = 0, len = properties.length; i < len; i++) {
          name = properties[i]

          for (prop in compatiblePrefixes) {
            if (compatiblePrefixes.hasOwnProperty(prop)) {
              variations = compatiblePrefixes[prop]
              if (CSSLint.Util.indexOf(variations, name.text) > -1) {
                if (!propertyGroups[prop]) {
                  propertyGroups[prop] = {
                    full: variations.slice(0),
                    actual: [],
                    actualNodes: []
                  }
                }
                if (CSSLint.Util.indexOf(propertyGroups[prop].actual, name.text) === -1) {
                  propertyGroups[prop].actual.push(name.text)
                  propertyGroups[prop].actualNodes.push(name)
                }
              }
            }
          }
        }

        for (prop in propertyGroups) {
          if (propertyGroups.hasOwnProperty(prop)) {
            value = propertyGroups[prop]
            full = value.full
            actual = value.actual

            if (full.length > actual.length) {
              for (i = 0, len = full.length; i < len; i++) {
                item = full[i]
                if (CSSLint.Util.indexOf(actual, item) === -1) {
                  propertiesSpecified = (actual.length === 1) ? actual[0] : (actual.length === 2) ? actual.join(' and ') : actual.join(', ')
                  reporter.report('The property ' + item + ' is compatible with ' + propertiesSpecified + ' and should be included as well.', value.actualNodes[0].line, value.actualNodes[0].col, rule)
                }
              }
            }
          }
        }
      })
    }
  })

  CSSLint.addRule({
    id: 'display-property-grouping',
    name: 'Require properties appropriate for display',
    desc: "Certain properties shouldn't be used with certain display property values.",
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this

      var propertiesToCheck = {
          display: 1,
          'float': 'none',
          height: 1,
          width: 1,
          margin: 1,
          'margin-left': 1,
          'margin-right': 1,
          'margin-bottom': 1,
          'margin-top': 1,
          padding: 1,
          'padding-left': 1,
          'padding-right': 1,
          'padding-bottom': 1,
          'padding-top': 1,
          'vertical-align': 1
        },
        properties

      function reportProperty(name, display, msg) {
        if (properties[name]) {
          if (typeof propertiesToCheck[name] !== 'string' || properties[name].value.toLowerCase() !== propertiesToCheck[name]) {
            reporter.report(msg || name + " can't be used with display: " + display + '.', properties[name].line, properties[name].col, rule)
          }
        }
      }

      function startRule() {
        properties = {}
      }

      function endRule() {
        var display = properties.display ? properties.display.value : null
        if (display) {
          switch (display) {
            case 'inline':
              reportProperty('height', display)
              reportProperty('width', display)
              reportProperty('margin', display)
              reportProperty('margin-top', display)
              reportProperty('margin-bottom', display)
              reportProperty('float', display, 'display:inline has no effect on floated elements (but may be used to fix the IE6 double-margin bug).')
              break

            case 'block':
              reportProperty('vertical-align', display)
              break

            case 'inline-block':
              reportProperty('float', display)
              break

            default:
              if (display.indexOf('table-') === 0) {
                reportProperty('margin', display)
                reportProperty('margin-left', display)
                reportProperty('margin-right', display)
                reportProperty('margin-top', display)
                reportProperty('margin-bottom', display)
                reportProperty('float', display)
              }
          }
        }
      }

      parser.addListener('startrule', startRule)
      parser.addListener('startfontface', startRule)
      parser.addListener('startkeyframerule', startRule)
      parser.addListener('startpagemargin', startRule)
      parser.addListener('startpage', startRule)

      parser.addListener('property', function(event) {
        var name = event.property.text.toLowerCase()

        if (propertiesToCheck[name]) {
          properties[name] = { value: event.value.text, line: event.property.line, col: event.property.col }
        }
      })

      parser.addListener('endrule', endRule)
      parser.addListener('endfontface', endRule)
      parser.addListener('endkeyframerule', endRule)
      parser.addListener('endpagemargin', endRule)
      parser.addListener('endpage', endRule)
    }

  })

  CSSLint.addRule({
    id: 'duplicate-background-images',
    name: 'Disallow duplicate background images',
    desc: 'Every background-image should be unique. Use a common class for e.g. sprites.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        stack = {}

      parser.addListener('property', function(event) {
        var name = event.property.text,
          value = event.value,
          i, len

        if (name.match(/background/i)) {
          for (i = 0, len = value.parts.length; i < len; i++) {
            if (value.parts[i].type === 'uri') {
              if (typeof stack[value.parts[i].uri] === 'undefined') {
                stack[value.parts[i].uri] = event
              } else {
                reporter.report("Background image '" + value.parts[i].uri + "' was used multiple times, first declared at line " + stack[value.parts[i].uri].line + ', col ' + stack[value.parts[i].uri].col + '.', event.line, event.col, rule)
              }
            }
          }
        }
      })
    }
  })

  CSSLint.addRule({
    id: 'duplicate-properties',
    name: 'Disallow duplicate properties',
    desc: 'Duplicate properties must appear one after the other.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        properties,
        lastProperty

      function startRule() {
        properties = {}
      }

      parser.addListener('startrule', startRule)
      parser.addListener('startfontface', startRule)
      parser.addListener('startpage', startRule)
      parser.addListener('startpagemargin', startRule)
      parser.addListener('startkeyframerule', startRule)

      parser.addListener('property', function(event) {
        var property = event.property,
          name = property.text.toLowerCase()

        if (properties[name] && (lastProperty !== name || properties[name] === event.value.text)) {
          reporter.report("Duplicate property '" + event.property + "' found.", event.line, event.col, rule)
        }

        properties[name] = event.value.text
        lastProperty = name
      })
    }

  })

  CSSLint.addRule({
    id: 'empty-rules',
    name: 'Disallow empty rules',
    desc: 'Rules without any properties specified should be removed.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        count = 0

      parser.addListener('startrule', function() {
        count = 0
      })

      parser.addListener('property', function() {
        count++
      })

      parser.addListener('endrule', function(event) {
        var selectors = event.selectors
        if (count === 0) {
          reporter.report('Rule is empty.', selectors[0].line, selectors[0].col, rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'errors',
    name: 'Parsing Errors',
    desc: 'This rule looks for recoverable syntax errors.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this

      parser.addListener('error', function(event) {
        reporter.error(event.message, event.line, event.col, rule)
      })
    }

  })

  CSSLint.addRule({
    id: 'fallback-colors',
    name: 'Require fallback colors',
    desc: "For older browsers that don't support RGBA, HSL, or HSLA, provide a fallback color.",
    browsers: 'IE6,IE7,IE8',
    init: function(parser, reporter) {
      var rule = this,
        lastProperty,
        propertiesToCheck = {
          color: 1,
          background: 1,
          'border-color': 1,
          'border-top-color': 1,
          'border-right-color': 1,
          'border-bottom-color': 1,
          'border-left-color': 1,
          border: 1,
          'border-top': 1,
          'border-right': 1,
          'border-bottom': 1,
          'border-left': 1,
          'background-color': 1
        },
        properties

      function startRule() {
        properties = {}
        lastProperty = null
      }

      parser.addListener('startrule', startRule)
      parser.addListener('startfontface', startRule)
      parser.addListener('startpage', startRule)
      parser.addListener('startpagemargin', startRule)
      parser.addListener('startkeyframerule', startRule)

      parser.addListener('property', function(event) {
        var property = event.property,
          name = property.text.toLowerCase(),
          parts = event.value.parts,
          i = 0,
          colorType = '',
          len = parts.length

        if (propertiesToCheck[name]) {
          while (i < len) {
            if (parts[i].type === 'color') {
              if ('alpha' in parts[i] || 'hue' in parts[i]) {
                if (/([^\)]+)\(/.test(parts[i])) {
                  colorType = RegExp.$1.toUpperCase()
                }

                if (!lastProperty || (lastProperty.property.text.toLowerCase() !== name || lastProperty.colorType !== 'compat')) {
                  reporter.report('Fallback ' + name + ' (hex or RGB) should precede ' + colorType + ' ' + name + '.', event.line, event.col, rule)
                }
              } else {
                event.colorType = 'compat'
              }
            }

            i++
          }
        }

        lastProperty = event
      })
    }

  })

  CSSLint.addRule({
    id: 'floats',
    name: 'Disallow too many floats',
    desc: 'This rule tests if the float property is used too many times',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this
      var count = 0
      parser.addListener('property', function(event) {
        if (event.property.text.toLowerCase() === 'float' &&
                    event.value.text.toLowerCase() !== 'none') {
          count++
        }
      })
      parser.addListener('endstylesheet', function() {
        reporter.stat('floats', count)
        if (count >= 10) {
          reporter.rollupWarn('Too many floats (' + count + "), you're probably using them for layout. Consider using a grid system instead.", rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'font-faces',
    name: "Don't use too many web fonts",
    desc: 'Too many different web fonts in the same stylesheet.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        count = 0

      parser.addListener('startfontface', function() {
        count++
      })

      parser.addListener('endstylesheet', function() {
        if (count > 5) {
          reporter.rollupWarn('Too many @font-face declarations (' + count + ').', rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'font-sizes',
    name: 'Disallow too many font sizes',
    desc: 'Checks the number of font-size declarations.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        count = 0
      parser.addListener('property', function(event) {
        if (event.property.toString() === 'font-size') {
          count++
        }
      })
      parser.addListener('endstylesheet', function() {
        reporter.stat('font-sizes', count)
        if (count >= 10) {
          reporter.rollupWarn('Too many font-size declarations (' + count + '), abstraction needed.', rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'gradients',
    name: 'Require all gradient definitions',
    desc: 'When using a vendor-prefixed gradient, make sure to use them all.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        gradients

      parser.addListener('startrule', function() {
        gradients = {
          moz: 0,
          webkit: 0,
          oldWebkit: 0,
          o: 0
        }
      })

      parser.addListener('property', function(event) {
        if (/\-(moz|o|webkit)(?:\-(?:linear|radial))\-gradient/i.test(event.value)) {
          gradients[RegExp.$1] = 1
        } else if (/\-webkit\-gradient/i.test(event.value)) {
          gradients.oldWebkit = 1
        }
      })

      parser.addListener('endrule', function(event) {
        var missing = []

        if (!gradients.moz) {
          missing.push('Firefox 3.6+')
        }

        if (!gradients.webkit) {
          missing.push('Webkit (Safari 5+, Chrome)')
        }

        if (!gradients.oldWebkit) {
          missing.push('Old Webkit (Safari 4+, Chrome)')
        }

        if (!gradients.o) {
          missing.push('Opera 11.1+')
        }

        if (missing.length && missing.length < 4) {
          reporter.report('Missing vendor-prefixed CSS gradients for ' + missing.join(', ') + '.', event.selectors[0].line, event.selectors[0].col, rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'ids',
    name: 'Disallow IDs in selectors',
    desc: 'Selectors should not contain IDs.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this
      parser.addListener('startrule', function(event) {
        var selectors = event.selectors,
          selector,
          part,
          modifier,
          idCount,
          i, j, k

        for (i = 0; i < selectors.length; i++) {
          selector = selectors[i]
          idCount = 0

          for (j = 0; j < selector.parts.length; j++) {
            part = selector.parts[j]
            if (part.type === parser.SELECTOR_PART_TYPE) {
              for (k = 0; k < part.modifiers.length; k++) {
                modifier = part.modifiers[k]
                if (modifier.type === 'id') {
                  idCount++
                }
              }
            }
          }

          if (idCount === 1) {
            reporter.report("Don't use IDs in selectors.", selector.line, selector.col, rule)
          } else if (idCount > 1) {
            reporter.report(idCount + ' IDs in the selector, really?', selector.line, selector.col, rule)
          }
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'import',
    name: 'Disallow @import',
    desc: "Don't use @import, use <link> instead.",
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this

      parser.addListener('import', function(event) {
        reporter.report('@import prevents parallel downloads, use <link> instead.', event.line, event.col, rule)
      })
    }

  })

  CSSLint.addRule({
    id: 'important',
    name: 'Disallow !important',
    desc: 'Be careful when using !important declaration',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        count = 0
      parser.addListener('property', function(event) {
        if (event.important === true) {
          count++
          reporter.report('Use of !important', event.line, event.col, rule)
        }
      })
      parser.addListener('endstylesheet', function() {
        reporter.stat('important', count)
        if (count >= 10) {
          reporter.rollupWarn('Too many !important declarations (' + count + '), try to use less than 10 to avoid specificity issues.', rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'known-properties',
    name: 'Require use of known properties',
    desc: 'Properties should be known (listed in CSS3 specification) or be a vendor-prefixed property.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this

      parser.addListener('property', function(event) {
        if (event.invalid) {
          reporter.report(event.invalid.message, event.line, event.col, rule)
        }
      })
    }

  })
  CSSLint.addRule({
    id: 'order-alphabetical',
    name: 'Alphabetical order',
    desc: 'Assure properties are in alphabetical order',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        properties

      var startRule = function() {
        properties = []
      }

      parser.addListener('startrule', startRule)
      parser.addListener('startfontface', startRule)
      parser.addListener('startpage', startRule)
      parser.addListener('startpagemargin', startRule)
      parser.addListener('startkeyframerule', startRule)

      parser.addListener('property', function(event) {
        var name = event.property.text,
          lowerCasePrefixLessName = name.toLowerCase().replace(/^-.*?-/, '')

        properties.push(lowerCasePrefixLessName)
      })

      parser.addListener('endrule', function(event) {
        var currentProperties = properties.join(','),
          expectedProperties = properties.sort().join(',')

        if (currentProperties !== expectedProperties) {
          reporter.report("Rule doesn't have all its properties in alphabetical ordered.", event.line, event.col, rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'outline-none',
    name: 'Disallow outline: none',
    desc: 'Use of outline: none or outline: 0 should be limited to :focus rules.',
    browsers: 'All',
    tags: ['Accessibility'],
    init: function(parser, reporter) {
      var rule = this,
        lastRule

      function startRule(event) {
        if (event.selectors) {
          lastRule = {
            line: event.line,
            col: event.col,
            selectors: event.selectors,
            propCount: 0,
            outline: false
          }
        } else {
          lastRule = null
        }
      }

      function endRule() {
        if (lastRule) {
          if (lastRule.outline) {
            if (lastRule.selectors.toString().toLowerCase().indexOf(':focus') === -1) {
              reporter.report('Outlines should only be modified using :focus.', lastRule.line, lastRule.col, rule)
            } else if (lastRule.propCount === 1) {
              reporter.report("Outlines shouldn't be hidden unless other visual changes are made.", lastRule.line, lastRule.col, rule)
            }
          }
        }
      }

      parser.addListener('startrule', startRule)
      parser.addListener('startfontface', startRule)
      parser.addListener('startpage', startRule)
      parser.addListener('startpagemargin', startRule)
      parser.addListener('startkeyframerule', startRule)

      parser.addListener('property', function(event) {
        var name = event.property.text.toLowerCase(),
          value = event.value

        if (lastRule) {
          lastRule.propCount++
          if (name === 'outline' && (value.toString() === 'none' || value.toString() === '0')) {
            lastRule.outline = true
          }
        }
      })

      parser.addListener('endrule', endRule)
      parser.addListener('endfontface', endRule)
      parser.addListener('endpage', endRule)
      parser.addListener('endpagemargin', endRule)
      parser.addListener('endkeyframerule', endRule)
    }

  })

  CSSLint.addRule({
    id: 'overqualified-elements',
    name: 'Disallow overqualified elements',
    desc: "Don't use classes or IDs with elements (a.foo or a#foo).",
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        classes = {}

      parser.addListener('startrule', function(event) {
        var selectors = event.selectors,
          selector,
          part,
          modifier,
          i, j, k

        for (i = 0; i < selectors.length; i++) {
          selector = selectors[i]

          for (j = 0; j < selector.parts.length; j++) {
            part = selector.parts[j]
            if (part.type === parser.SELECTOR_PART_TYPE) {
              for (k = 0; k < part.modifiers.length; k++) {
                modifier = part.modifiers[k]
                if (part.elementName && modifier.type === 'id') {
                  reporter.report('Element (' + part + ') is overqualified, just use ' + modifier + ' without element name.', part.line, part.col, rule)
                } else if (modifier.type === 'class') {
                  if (!classes[modifier]) {
                    classes[modifier] = []
                  }
                  classes[modifier].push({ modifier: modifier, part: part })
                }
              }
            }
          }
        }
      })

      parser.addListener('endstylesheet', function() {
        var prop
        for (prop in classes) {
          if (classes.hasOwnProperty(prop)) {
            if (classes[prop].length === 1 && classes[prop][0].part.elementName) {
              reporter.report('Element (' + classes[prop][0].part + ') is overqualified, just use ' + classes[prop][0].modifier + ' without element name.', classes[prop][0].part.line, classes[prop][0].part.col, rule)
            }
          }
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'qualified-headings',
    name: 'Disallow qualified headings',
    desc: 'Headings should not be qualified (namespaced).',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this

      parser.addListener('startrule', function(event) {
        var selectors = event.selectors,
          selector,
          part,
          i, j

        for (i = 0; i < selectors.length; i++) {
          selector = selectors[i]

          for (j = 0; j < selector.parts.length; j++) {
            part = selector.parts[j]
            if (part.type === parser.SELECTOR_PART_TYPE) {
              if (part.elementName && /h[1-6]/.test(part.elementName.toString()) && j > 0) {
                reporter.report('Heading (' + part.elementName + ') should not be qualified.', part.line, part.col, rule)
              }
            }
          }
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'regex-selectors',
    name: 'Disallow selectors that look like regexs',
    desc: 'Selectors that look like regular expressions are slow and should be avoided.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this

      parser.addListener('startrule', function(event) {
        var selectors = event.selectors,
          selector,
          part,
          modifier,
          i, j, k

        for (i = 0; i < selectors.length; i++) {
          selector = selectors[i]
          for (j = 0; j < selector.parts.length; j++) {
            part = selector.parts[j]
            if (part.type === parser.SELECTOR_PART_TYPE) {
              for (k = 0; k < part.modifiers.length; k++) {
                modifier = part.modifiers[k]
                if (modifier.type === 'attribute') {
                  if (/([\~\|\^\$\*]=)/.test(modifier)) {
                    reporter.report('Attribute selectors with ' + RegExp.$1 + ' are slow!', modifier.line, modifier.col, rule)
                  }
                }
              }
            }
          }
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'rules-count',
    name: 'Rules Count',
    desc: 'Track how many rules there are.',
    browsers: 'All',
    init: function(parser, reporter) {
      var count = 0
      parser.addListener('startrule', function() {
        count++
      })

      parser.addListener('endstylesheet', function() {
        reporter.stat('rule-count', count)
      })
    }

  })

  CSSLint.addRule({
    id: 'selector-max-approaching',
    name: 'Warn when approaching the 4095 selector limit for IE',
    desc: 'Will warn when selector count is >= 3800 selectors.',
    browsers: 'IE',
    init: function(parser, reporter) {
      var rule = this, count = 0

      parser.addListener('startrule', function(event) {
        count += event.selectors.length
      })

      parser.addListener('endstylesheet', function() {
        if (count >= 3800) {
          reporter.report('You have ' + count + ' selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.', 0, 0, rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'selector-max',
    name: 'Error when past the 4095 selector limit for IE',
    desc: 'Will error when selector count is > 4095.',
    browsers: 'IE',
    init: function(parser, reporter) {
      var rule = this, count = 0

      parser.addListener('startrule', function(event) {
        count += event.selectors.length
      })

      parser.addListener('endstylesheet', function() {
        if (count > 4095) {
          reporter.report('You have ' + count + ' selectors. Internet Explorer supports a maximum of 4095 selectors per stylesheet. Consider refactoring.', 0, 0, rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'selector-newline',
    name: 'Disallow new-line characters in selectors',
    desc: 'New-line characters in selectors are usually a forgotten comma and not a descendant combinator.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this

      function startRule(event) {
        var i, len, selector, p, n, pLen, part, part2, type, currentLine, nextLine,
          selectors = event.selectors

        for (i = 0, len = selectors.length; i < len; i++) {
          selector = selectors[i]
          for (p = 0, pLen = selector.parts.length; p < pLen; p++) {
            for (n = p + 1; n < pLen; n++) {
              part = selector.parts[p]
              part2 = selector.parts[n]
              type = part.type
              currentLine = part.line
              nextLine = part2.line

              if (type === 'descendant' && nextLine > currentLine) {
                reporter.report('newline character found in selector (forgot a comma?)', currentLine, selectors[i].parts[0].col, rule)
              }
            }
          }
        }
      }

      parser.addListener('startrule', startRule)
    }
  })

  CSSLint.addRule({
    id: 'shorthand',
    name: 'Require shorthand properties',
    desc: 'Use shorthand properties where possible.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        prop, i, len,
        propertiesToCheck = {},
        properties,
        mapping = {
          'margin': [
            'margin-top',
            'margin-bottom',
            'margin-left',
            'margin-right'
          ],
          'padding': [
            'padding-top',
            'padding-bottom',
            'padding-left',
            'padding-right'
          ]
        }
      for (prop in mapping) {
        if (mapping.hasOwnProperty(prop)) {
          for (i = 0, len = mapping[prop].length; i < len; i++) {
            propertiesToCheck[mapping[prop][i]] = prop
          }
        }
      }

      function startRule() {
        properties = {}
      }
      function endRule(event) {
        var prop, i, len, total
        for (prop in mapping) {
          if (mapping.hasOwnProperty(prop)) {
            total = 0

            for (i = 0, len = mapping[prop].length; i < len; i++) {
              total += properties[mapping[prop][i]] ? 1 : 0
            }

            if (total === mapping[prop].length) {
              reporter.report('The properties ' + mapping[prop].join(', ') + ' can be replaced by ' + prop + '.', event.line, event.col, rule)
            }
          }
        }
      }

      parser.addListener('startrule', startRule)
      parser.addListener('startfontface', startRule)
      parser.addListener('property', function(event) {
        var name = event.property.toString().toLowerCase()

        if (propertiesToCheck[name]) {
          properties[name] = 1
        }
      })

      parser.addListener('endrule', endRule)
      parser.addListener('endfontface', endRule)
    }

  })

  CSSLint.addRule({
    id: 'star-property-hack',
    name: 'Disallow properties with a star prefix',
    desc: 'Checks for the star property hack (targets IE6/7)',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this
      parser.addListener('property', function(event) {
        var property = event.property

        if (property.hack === '*') {
          reporter.report('Property with star prefix found.', event.property.line, event.property.col, rule)
        }
      })
    }
  })

  CSSLint.addRule({
    id: 'text-indent',
    name: 'Disallow negative text-indent',
    desc: 'Checks for text indent less than -99px',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        textIndent,
        direction

      function startRule() {
        textIndent = false
        direction = 'inherit'
      }
      function endRule() {
        if (textIndent && direction !== 'ltr') {
          reporter.report("Negative text-indent doesn't work well with RTL. If you use text-indent for image replacement explicitly set direction for that item to ltr.", textIndent.line, textIndent.col, rule)
        }
      }

      parser.addListener('startrule', startRule)
      parser.addListener('startfontface', startRule)
      parser.addListener('property', function(event) {
        var name = event.property.toString().toLowerCase(),
          value = event.value

        if (name === 'text-indent' && value.parts[0].value < -99) {
          textIndent = event.property
        } else if (name === 'direction' && value.toString() === 'ltr') {
          direction = 'ltr'
        }
      })

      parser.addListener('endrule', endRule)
      parser.addListener('endfontface', endRule)
    }

  })

  CSSLint.addRule({
    id: 'underscore-property-hack',
    name: 'Disallow properties with an underscore prefix',
    desc: 'Checks for the underscore property hack (targets IE6)',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this
      parser.addListener('property', function(event) {
        var property = event.property

        if (property.hack === '_') {
          reporter.report('Property with underscore prefix found.', event.property.line, event.property.col, rule)
        }
      })
    }
  })

  CSSLint.addRule({
    id: 'unique-headings',
    name: 'Headings should only be defined once',
    desc: 'Headings should be defined only once.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this

      var headings = {
        h1: 0,
        h2: 0,
        h3: 0,
        h4: 0,
        h5: 0,
        h6: 0
      }

      parser.addListener('startrule', function(event) {
        var selectors = event.selectors,
          selector,
          part,
          pseudo,
          i, j

        for (i = 0; i < selectors.length; i++) {
          selector = selectors[i]
          part = selector.parts[selector.parts.length - 1]

          if (part.elementName && /(h[1-6])/i.test(part.elementName.toString())) {
            for (j = 0; j < part.modifiers.length; j++) {
              if (part.modifiers[j].type === 'pseudo') {
                pseudo = true
                break
              }
            }

            if (!pseudo) {
              headings[RegExp.$1]++
              if (headings[RegExp.$1] > 1) {
                reporter.report('Heading (' + part.elementName + ') has already been defined.', part.line, part.col, rule)
              }
            }
          }
        }
      })

      parser.addListener('endstylesheet', function() {
        var prop,
          messages = []

        for (prop in headings) {
          if (headings.hasOwnProperty(prop)) {
            if (headings[prop] > 1) {
              messages.push(headings[prop] + ' ' + prop + 's')
            }
          }
        }

        if (messages.length) {
          reporter.rollupWarn('You have ' + messages.join(', ') + ' defined in this stylesheet.', rule)
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'universal-selector',
    name: 'Disallow universal selector',
    desc: 'The universal selector (*) is known to be slow.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this

      parser.addListener('startrule', function(event) {
        var selectors = event.selectors,
          selector,
          part,
          i

        for (i = 0; i < selectors.length; i++) {
          selector = selectors[i]

          part = selector.parts[selector.parts.length - 1]
          if (part.elementName === '*') {
            reporter.report(rule.desc, part.line, part.col, rule)
          }
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'unqualified-attributes',
    name: 'Disallow unqualified attribute selectors',
    desc: 'Unqualified attribute selectors are known to be slow.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this

      parser.addListener('startrule', function(event) {
        var selectors = event.selectors,
          selector,
          part,
          modifier,
          i, k

        for (i = 0; i < selectors.length; i++) {
          selector = selectors[i]

          part = selector.parts[selector.parts.length - 1]
          if (part.type === parser.SELECTOR_PART_TYPE) {
            for (k = 0; k < part.modifiers.length; k++) {
              modifier = part.modifiers[k]
              if (modifier.type === 'attribute' && (!part.elementName || part.elementName === '*')) {
                reporter.report(rule.desc, part.line, part.col, rule)
              }
            }
          }
        }
      })
    }

  })

  CSSLint.addRule({
    id: 'vendor-prefix',
    name: 'Require standard property with vendor prefix',
    desc: 'When using a vendor-prefixed property, make sure to include the standard one.',
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this,
        properties,
        num,
        propertiesToCheck = {
          '-webkit-border-radius': 'border-radius',
          '-webkit-border-top-left-radius': 'border-top-left-radius',
          '-webkit-border-top-right-radius': 'border-top-right-radius',
          '-webkit-border-bottom-left-radius': 'border-bottom-left-radius',
          '-webkit-border-bottom-right-radius': 'border-bottom-right-radius',

          '-o-border-radius': 'border-radius',
          '-o-border-top-left-radius': 'border-top-left-radius',
          '-o-border-top-right-radius': 'border-top-right-radius',
          '-o-border-bottom-left-radius': 'border-bottom-left-radius',
          '-o-border-bottom-right-radius': 'border-bottom-right-radius',

          '-moz-border-radius': 'border-radius',
          '-moz-border-radius-topleft': 'border-top-left-radius',
          '-moz-border-radius-topright': 'border-top-right-radius',
          '-moz-border-radius-bottomleft': 'border-bottom-left-radius',
          '-moz-border-radius-bottomright': 'border-bottom-right-radius',

          '-moz-column-count': 'column-count',
          '-webkit-column-count': 'column-count',

          '-moz-column-gap': 'column-gap',
          '-webkit-column-gap': 'column-gap',

          '-moz-column-rule': 'column-rule',
          '-webkit-column-rule': 'column-rule',

          '-moz-column-rule-style': 'column-rule-style',
          '-webkit-column-rule-style': 'column-rule-style',

          '-moz-column-rule-color': 'column-rule-color',
          '-webkit-column-rule-color': 'column-rule-color',

          '-moz-column-rule-width': 'column-rule-width',
          '-webkit-column-rule-width': 'column-rule-width',

          '-moz-column-width': 'column-width',
          '-webkit-column-width': 'column-width',

          '-webkit-column-span': 'column-span',
          '-webkit-columns': 'columns',

          '-moz-box-shadow': 'box-shadow',
          '-webkit-box-shadow': 'box-shadow',

          '-moz-transform': 'transform',
          '-webkit-transform': 'transform',
          '-o-transform': 'transform',
          '-ms-transform': 'transform',

          '-moz-transform-origin': 'transform-origin',
          '-webkit-transform-origin': 'transform-origin',
          '-o-transform-origin': 'transform-origin',
          '-ms-transform-origin': 'transform-origin',

          '-moz-box-sizing': 'box-sizing',
          '-webkit-box-sizing': 'box-sizing'
        }
      function startRule() {
        properties = {}
        num = 1
      }
      function endRule() {
        var prop,
          i,
          len,
          needed,
          actual,
          needsStandard = []

        for (prop in properties) {
          if (propertiesToCheck[prop]) {
            needsStandard.push({ actual: prop, needed: propertiesToCheck[prop] })
          }
        }

        for (i = 0, len = needsStandard.length; i < len; i++) {
          needed = needsStandard[i].needed
          actual = needsStandard[i].actual

          if (!properties[needed]) {
            reporter.report("Missing standard property '" + needed + "' to go along with '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule)
          } else {
            if (properties[needed][0].pos < properties[actual][0].pos) {
              reporter.report("Standard property '" + needed + "' should come after vendor-prefixed property '" + actual + "'.", properties[actual][0].name.line, properties[actual][0].name.col, rule)
            }
          }
        }
      }

      parser.addListener('startrule', startRule)
      parser.addListener('startfontface', startRule)
      parser.addListener('startpage', startRule)
      parser.addListener('startpagemargin', startRule)
      parser.addListener('startkeyframerule', startRule)

      parser.addListener('property', function(event) {
        var name = event.property.text.toLowerCase()

        if (!properties[name]) {
          properties[name] = []
        }

        properties[name].push({ name: event.property, value: event.value, pos: num++ })
      })

      parser.addListener('endrule', endRule)
      parser.addListener('endfontface', endRule)
      parser.addListener('endpage', endRule)
      parser.addListener('endpagemargin', endRule)
      parser.addListener('endkeyframerule', endRule)
    }

  })

  CSSLint.addRule({
    id: 'zero-units',
    name: 'Disallow units for 0 values',
    desc: "You don't need to specify units when a value is 0.",
    browsers: 'All',
    init: function(parser, reporter) {
      var rule = this
      parser.addListener('property', function(event) {
        var parts = event.value.parts,
          i = 0,
          len = parts.length

        while (i < len) {
          if ((parts[i].units || parts[i].type === 'percentage') && parts[i].value === 0 && parts[i].type !== 'time') {
            reporter.report("Values of 0 shouldn't have units specified.", parts[i].line, parts[i].col, rule)
          }
          i++
        }
      })
    }

  });

  (function() {
    var xmlEscape = function(str) {
      if (!str || str.constructor !== String) {
        return ''
      }

      return str.replace(/[\"&><]/g, function(match) {
        switch (match) {
          case '"':
            return '&quot;'
          case '&':
            return '&amp;'
          case '<':
            return '&lt;'
          case '>':
            return '&gt;'
        }
      })
    }

    CSSLint.addFormatter({
      id: 'checkstyle-xml',
      name: 'Checkstyle XML format',
      startFormat: function() {
        return '<?xml version="1.0" encoding="utf-8"?><checkstyle>'
      },
      endFormat: function() {
        return '</checkstyle>'
      },
      readError: function(filename, message) {
        return '<file name="' + xmlEscape(filename) + '"><error line="0" column="0" severty="error" message="' + xmlEscape(message) + '"></error></file>'
      },
      formatResults: function(results, filename/*, options*/) {
        var messages = results.messages,
          output = []
        var generateSource = function(rule) {
          if (!rule || !('name' in rule)) {
            return ''
          }
          return 'net.csslint.' + rule.name.replace(/\s/g, '')
        }

        if (messages.length > 0) {
          output.push('<file name="' + filename + '">')
          CSSLint.Util.forEach(messages, function(message) {
            if (!message.rollup) {
              output.push('<error line="' + message.line + '" column="' + message.col + '" severity="' + message.type + '"' +
                          ' message="' + xmlEscape(message.message) + '" source="' + generateSource(message.rule) + '"/>')
            }
          })
          output.push('</file>')
        }

        return output.join('')
      }
    })
  }())

  CSSLint.addFormatter({
    id: 'compact',
    name: "Compact, 'porcelain' format",
    startFormat: function() {
      return ''
    },
    endFormat: function() {
      return ''
    },
    formatResults: function(results, filename, options) {
      var messages = results.messages,
        output = ''
      options = options || {}
      var capitalize = function(str) {
        return str.charAt(0).toUpperCase() + str.slice(1)
      }

      if (messages.length === 0) {
        return options.quiet ? '' : filename + ': Lint Free!'
      }

      CSSLint.Util.forEach(messages, function(message) {
        if (message.rollup) {
          output += filename + ': ' + capitalize(message.type) + ' - ' + message.message + '\n'
        } else {
          output += filename + ': ' + 'line ' + message.line +
                    ', col ' + message.col + ', ' + capitalize(message.type) + ' - ' + message.message + ' (' + message.rule.id + ')\n'
        }
      })

      return output
    }
  })

  CSSLint.addFormatter({
    id: 'csslint-xml',
    name: 'CSSLint XML format',
    startFormat: function() {
      return '<?xml version="1.0" encoding="utf-8"?><csslint>'
    },
    endFormat: function() {
      return '</csslint>'
    },
    formatResults: function(results, filename/*, options*/) {
      var messages = results.messages,
        output = []
      var escapeSpecialCharacters = function(str) {
        if (!str || str.constructor !== String) {
          return ''
        }
        return str.replace(/\"/g, "'").replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
      }

      if (messages.length > 0) {
        output.push('<file name="' + filename + '">')
        CSSLint.Util.forEach(messages, function(message) {
          if (message.rollup) {
            output.push('<issue severity="' + message.type + '" reason="' + escapeSpecialCharacters(message.message) + '" evidence="' + escapeSpecialCharacters(message.evidence) + '"/>')
          } else {
            output.push('<issue line="' + message.line + '" char="' + message.col + '" severity="' + message.type + '"' +
                        ' reason="' + escapeSpecialCharacters(message.message) + '" evidence="' + escapeSpecialCharacters(message.evidence) + '"/>')
          }
        })
        output.push('</file>')
      }

      return output.join('')
    }
  })

  CSSLint.addFormatter({
    id: 'junit-xml',
    name: 'JUNIT XML format',
    startFormat: function() {
      return '<?xml version="1.0" encoding="utf-8"?><testsuites>'
    },
    endFormat: function() {
      return '</testsuites>'
    },
    formatResults: function(results, filename/*, options*/) {
      var messages = results.messages,
        output = [],
        tests = {
          'error': 0,
          'failure': 0
        }
      var generateSource = function(rule) {
        if (!rule || !('name' in rule)) {
          return ''
        }
        return 'net.csslint.' + rule.name.replace(/\s/g, '')
      }
      var escapeSpecialCharacters = function(str) {
        if (!str || str.constructor !== String) {
          return ''
        }

        return str.replace(/\"/g, "'").replace(/</g, '&lt;').replace(/>/g, '&gt;')
      }

      if (messages.length > 0) {
        messages.forEach(function(message) {
          var type = message.type === 'warning' ? 'error' : message.type
          if (!message.rollup) {
            output.push('<testcase time="0" name="' + generateSource(message.rule) + '">')
            output.push('<' + type + ' message="' + escapeSpecialCharacters(message.message) + '"><![CDATA[' + message.line + ':' + message.col + ':' + escapeSpecialCharacters(message.evidence) + ']]></' + type + '>')
            output.push('</testcase>')

            tests[type] += 1
          }
        })

        output.unshift('<testsuite time="0" tests="' + messages.length + '" skipped="0" errors="' + tests.error + '" failures="' + tests.failure + '" package="net.csslint" name="' + filename + '">')
        output.push('</testsuite>')
      }

      return output.join('')
    }
  })

  CSSLint.addFormatter({
    id: 'lint-xml',
    name: 'Lint XML format',
    startFormat: function() {
      return '<?xml version="1.0" encoding="utf-8"?><lint>'
    },
    endFormat: function() {
      return '</lint>'
    },
    formatResults: function(results, filename/*, options*/) {
      var messages = results.messages,
        output = []
      var escapeSpecialCharacters = function(str) {
        if (!str || str.constructor !== String) {
          return ''
        }
        return str.replace(/\"/g, "'").replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
      }

      if (messages.length > 0) {
        output.push('<file name="' + filename + '">')
        CSSLint.Util.forEach(messages, function(message) {
          if (message.rollup) {
            output.push('<issue severity="' + message.type + '" reason="' + escapeSpecialCharacters(message.message) + '" evidence="' + escapeSpecialCharacters(message.evidence) + '"/>')
          } else {
            output.push('<issue line="' + message.line + '" char="' + message.col + '" severity="' + message.type + '"' +
                        ' reason="' + escapeSpecialCharacters(message.message) + '" evidence="' + escapeSpecialCharacters(message.evidence) + '"/>')
          }
        })
        output.push('</file>')
      }

      return output.join('')
    }
  })

  CSSLint.addFormatter({
    id: 'text',
    name: 'Plain Text',
    startFormat: function() {
      return ''
    },
    endFormat: function() {
      return ''
    },
    formatResults: function(results, filename, options) {
      var messages = results.messages,
        output = ''
      options = options || {}

      if (messages.length === 0) {
        return options.quiet ? '' : '\n\ncsslint: No errors in ' + filename + '.'
      }

      output = '\n\ncsslint: There '
      if (messages.length === 1) {
        output += 'is 1 problem'
      } else {
        output += 'are ' + messages.length + ' problems'
      }
      output += ' in ' + filename + '.'

      var pos = filename.lastIndexOf('/'),
        shortFilename = filename

      if (pos === -1) {
        pos = filename.lastIndexOf('\\')
      }
      if (pos > -1) {
        shortFilename = filename.substring(pos + 1)
      }

      CSSLint.Util.forEach(messages, function(message, i) {
        output = output + '\n\n' + shortFilename
        if (message.rollup) {
          output += '\n' + (i + 1) + ': ' + message.type
          output += '\n' + message.message
        } else {
          output += '\n' + (i + 1) + ': ' + message.type + ' at line ' + message.line + ', col ' + message.col
          output += '\n' + message.message
          output += '\n' + message.evidence
        }
      })

      return output
    }
  })

  module.exports.CSSLint = CSSLint
})

define('ace/mode/css_worker', [], function(require, exports, module) {
  'use strict'

  var oop = require('../lib/oop')
  var lang = require('../lib/lang')
  var Mirror = require('../worker/mirror').Mirror
  var CSSLint = require('./css/csslint').CSSLint

  var Worker = exports.Worker = function(sender) {
    Mirror.call(this, sender)
    this.setTimeout(400)
    this.ruleset = null
    this.setDisabledRules('ids|order-alphabetical')
    this.setInfoRules(
      'adjoining-classes|qualified-headings|zero-units|gradients|' +
      'import|outline-none|vendor-prefix'
    )
  }

  oop.inherits(Worker, Mirror);

  (function() {
    this.setInfoRules = function(ruleNames) {
      if (typeof ruleNames === 'string') { ruleNames = ruleNames.split('|') }
      this.infoRules = lang.arrayToMap(ruleNames)
      this.doc.getValue() && this.deferredUpdate.schedule(100)
    }

    this.setDisabledRules = function(ruleNames) {
      if (!ruleNames) {
        this.ruleset = null
      } else {
        if (typeof ruleNames === 'string') { ruleNames = ruleNames.split('|') }
        var all = {}

        CSSLint.getRules().forEach(function(x) {
          all[x.id] = true
        })
        ruleNames.forEach(function(x) {
          delete all[x]
        })

        this.ruleset = all
      }
      this.doc.getValue() && this.deferredUpdate.schedule(100)
    }

    this.onUpdate = function() {
      var value = this.doc.getValue()
      if (!value) { return this.sender.emit('annotate', []) }
      var infoRules = this.infoRules

      var result = CSSLint.verify(value, this.ruleset)
      this.sender.emit('annotate', result.messages.map(function(msg) {
        return {
          row: msg.line - 1,
          column: msg.col - 1,
          text: msg.message,
          type: infoRules[msg.rule.id] ? 'info' : msg.type,
          rule: msg.rule.name
        }
      }))
    }
  }).call(Worker.prototype)
})

define('ace/lib/es5-shim', [], function(require, exports, module) {
//
//
  function Empty() {}

  if (!Function.prototype.bind) {
    Function.prototype.bind = function bind(that) { // .length is 1
      var target = this
      if (typeof target !== 'function') {
        throw new TypeError('Function.prototype.bind called on incompatible ' + target)
      }
      var args = slice.call(arguments, 1) // for normal call
      var bound = function() {
        if (this instanceof bound) {
          var result = target.apply(
            this,
            args.concat(slice.call(arguments))
          )
          if (Object(result) === result) {
            return result
          }
          return this
        } else {
          return target.apply(
            that,
            args.concat(slice.call(arguments))
          )
        }
      }
      if (target.prototype) {
        Empty.prototype = target.prototype
        bound.prototype = new Empty()
        Empty.prototype = null
      }
      //
      return bound
    }
  }
  var call = Function.prototype.call
  var prototypeOfArray = Array.prototype
  var prototypeOfObject = Object.prototype
  var slice = prototypeOfArray.slice
  var _toString = call.bind(prototypeOfObject.toString)
  var owns = call.bind(prototypeOfObject.hasOwnProperty)
  var defineGetter
  var defineSetter
  var lookupGetter
  var lookupSetter
  var supportsAccessors
  if ((supportsAccessors = owns(prototypeOfObject, '__defineGetter__'))) {
    defineGetter = call.bind(prototypeOfObject.__defineGetter__)
    defineSetter = call.bind(prototypeOfObject.__defineSetter__)
    lookupGetter = call.bind(prototypeOfObject.__lookupGetter__)
    lookupSetter = call.bind(prototypeOfObject.__lookupSetter__)
  }

  //
  //
  if ([1, 2].splice(0).length != 2) {
    if (function() { // test IE < 9 to splice bug - see issue #138
      function makeArray(l) {
        var a = new Array(l + 2)
        a[0] = a[1] = 0
        return a
      }
      var array = [], lengthBefore

      array.splice.apply(array, makeArray(20))
      array.splice.apply(array, makeArray(26))

      lengthBefore = array.length // 46
      array.splice(5, 0, 'XXX') // add one element

      lengthBefore + 1 == array.length

      if (lengthBefore + 1 == array.length) {
        return true// has right splice implementation without bugs
      }
    }()) { // IE 6/7
      var array_splice = Array.prototype.splice
      Array.prototype.splice = function(start, deleteCount) {
        if (!arguments.length) {
          return []
        } else {
          return array_splice.apply(this, [
            start === void 0 ? 0 : start,
            deleteCount === void 0 ? (this.length - start) : deleteCount
          ].concat(slice.call(arguments, 2)))
        }
      }
    } else { // IE8
      Array.prototype.splice = function(pos, removeCount) {
        var length = this.length
        if (pos > 0) {
          if (pos > length) { pos = length }
        } else if (pos == void 0) {
          pos = 0
        } else if (pos < 0) {
          pos = Math.max(length + pos, 0)
        }

        if (!(pos + removeCount < length)) { removeCount = length - pos }

        var removed = this.slice(pos, pos + removeCount)
        var insert = slice.call(arguments, 2)
        var add = insert.length
        if (pos === length) {
          if (add) {
            this.push.apply(this, insert)
          }
        } else {
          var remove = Math.min(removeCount, length - pos)
          var tailOldPos = pos + remove
          var tailNewPos = tailOldPos + add - remove
          var tailCount = length - tailOldPos
          var lengthAfterRemove = length - remove

          if (tailNewPos < tailOldPos) { // case A
            for (var i = 0; i < tailCount; ++i) {
              this[tailNewPos + i] = this[tailOldPos + i]
            }
          } else if (tailNewPos > tailOldPos) { // case B
            for (i = tailCount; i--;) {
              this[tailNewPos + i] = this[tailOldPos + i]
            }
          } // else, add == remove (nothing to do)

          if (add && pos === lengthAfterRemove) {
            this.length = lengthAfterRemove // truncate array
            this.push.apply(this, insert)
          } else {
            this.length = lengthAfterRemove + add // reserves space
            for (i = 0; i < add; ++i) {
              this[pos + i] = insert[i]
            }
          }
        }
        return removed
      }
    }
  }
  if (!Array.isArray) {
    Array.isArray = function isArray(obj) {
      return _toString(obj) == '[object Array]'
    }
  }
  var boxedString = Object('a'),
    splitString = boxedString[0] != 'a' || !(0 in boxedString)

  if (!Array.prototype.forEach) {
    Array.prototype.forEach = function forEach(fun /*, thisp*/) {
      var object = toObject(this),
        self = splitString && _toString(this) == '[object String]'
          ? this.split('')
          : object,
        thisp = arguments[1],
        i = -1,
        length = self.length >>> 0
      if (_toString(fun) != '[object Function]') {
        throw new TypeError() // TODO message
      }

      while (++i < length) {
        if (i in self) {
          fun.call(thisp, self[i], i, object)
        }
      }
    }
  }
  if (!Array.prototype.map) {
    Array.prototype.map = function map(fun /*, thisp*/) {
      var object = toObject(this),
        self = splitString && _toString(this) == '[object String]'
          ? this.split('')
          : object,
        length = self.length >>> 0,
        result = Array(length),
        thisp = arguments[1]
      if (_toString(fun) != '[object Function]') {
        throw new TypeError(fun + ' is not a function')
      }

      for (var i = 0; i < length; i++) {
        if (i in self) { result[i] = fun.call(thisp, self[i], i, object) }
      }
      return result
    }
  }
  if (!Array.prototype.filter) {
    Array.prototype.filter = function filter(fun /*, thisp */) {
      var object = toObject(this),
        self = splitString && _toString(this) == '[object String]'
          ? this.split('')
          : object,
        length = self.length >>> 0,
        result = [],
        value,
        thisp = arguments[1]
      if (_toString(fun) != '[object Function]') {
        throw new TypeError(fun + ' is not a function')
      }

      for (var i = 0; i < length; i++) {
        if (i in self) {
          value = self[i]
          if (fun.call(thisp, value, i, object)) {
            result.push(value)
          }
        }
      }
      return result
    }
  }
  if (!Array.prototype.every) {
    Array.prototype.every = function every(fun /*, thisp */) {
      var object = toObject(this),
        self = splitString && _toString(this) == '[object String]'
          ? this.split('')
          : object,
        length = self.length >>> 0,
        thisp = arguments[1]
      if (_toString(fun) != '[object Function]') {
        throw new TypeError(fun + ' is not a function')
      }

      for (var i = 0; i < length; i++) {
        if (i in self && !fun.call(thisp, self[i], i, object)) {
          return false
        }
      }
      return true
    }
  }
  if (!Array.prototype.some) {
    Array.prototype.some = function some(fun /*, thisp */) {
      var object = toObject(this),
        self = splitString && _toString(this) == '[object String]'
          ? this.split('')
          : object,
        length = self.length >>> 0,
        thisp = arguments[1]
      if (_toString(fun) != '[object Function]') {
        throw new TypeError(fun + ' is not a function')
      }

      for (var i = 0; i < length; i++) {
        if (i in self && fun.call(thisp, self[i], i, object)) {
          return true
        }
      }
      return false
    }
  }
  if (!Array.prototype.reduce) {
    Array.prototype.reduce = function reduce(fun /*, initial*/) {
      var object = toObject(this),
        self = splitString && _toString(this) == '[object String]'
          ? this.split('')
          : object,
        length = self.length >>> 0
      if (_toString(fun) != '[object Function]') {
        throw new TypeError(fun + ' is not a function')
      }
      if (!length && arguments.length == 1) {
        throw new TypeError('reduce of empty array with no initial value')
      }

      var i = 0
      var result
      if (arguments.length >= 2) {
        result = arguments[1]
      } else {
        do {
          if (i in self) {
            result = self[i++]
            break
          }
          if (++i >= length) {
            throw new TypeError('reduce of empty array with no initial value')
          }
        } while (true)
      }

      for (; i < length; i++) {
        if (i in self) {
          result = fun.call(void 0, result, self[i], i, object)
        }
      }

      return result
    }
  }
  if (!Array.prototype.reduceRight) {
    Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) {
      var object = toObject(this),
        self = splitString && _toString(this) == '[object String]'
          ? this.split('')
          : object,
        length = self.length >>> 0
      if (_toString(fun) != '[object Function]') {
        throw new TypeError(fun + ' is not a function')
      }
      if (!length && arguments.length == 1) {
        throw new TypeError('reduceRight of empty array with no initial value')
      }

      var result, i = length - 1
      if (arguments.length >= 2) {
        result = arguments[1]
      } else {
        do {
          if (i in self) {
            result = self[i--]
            break
          }
          if (--i < 0) {
            throw new TypeError('reduceRight of empty array with no initial value')
          }
        } while (true)
      }

      do {
        if (i in this) {
          result = fun.call(void 0, result, self[i], i, object)
        }
      } while (i--)

      return result
    }
  }
  if (!Array.prototype.indexOf || ([0, 1].indexOf(1, 2) != -1)) {
    Array.prototype.indexOf = function indexOf(sought /*, fromIndex */) {
      var self = splitString && _toString(this) == '[object String]'
          ? this.split('')
          : toObject(this),
        length = self.length >>> 0

      if (!length) {
        return -1
      }

      var i = 0
      if (arguments.length > 1) {
        i = toInteger(arguments[1])
      }
      i = i >= 0 ? i : Math.max(0, length + i)
      for (; i < length; i++) {
        if (i in self && self[i] === sought) {
          return i
        }
      }
      return -1
    }
  }
  if (!Array.prototype.lastIndexOf || ([0, 1].lastIndexOf(0, -3) != -1)) {
    Array.prototype.lastIndexOf = function lastIndexOf(sought /*, fromIndex */) {
      var self = splitString && _toString(this) == '[object String]'
          ? this.split('')
          : toObject(this),
        length = self.length >>> 0

      if (!length) {
        return -1
      }
      var i = length - 1
      if (arguments.length > 1) {
        i = Math.min(i, toInteger(arguments[1]))
      }
      i = i >= 0 ? i : length - Math.abs(i)
      for (; i >= 0; i--) {
        if (i in self && sought === self[i]) {
          return i
        }
      }
      return -1
    }
  }

  //
  //
  if (!Object.getPrototypeOf) {
    Object.getPrototypeOf = function getPrototypeOf(object) {
      return object.__proto__ || (
        object.constructor
          ? object.constructor.prototype
          : prototypeOfObject
      )
    }
  }
  if (!Object.getOwnPropertyDescriptor) {
    var ERR_NON_OBJECT = 'Object.getOwnPropertyDescriptor called on a ' +
                         'non-object: '
    Object.getOwnPropertyDescriptor = function getOwnPropertyDescriptor(object, property) {
      if ((typeof object !== 'object' && typeof object !== 'function') || object === null) { throw new TypeError(ERR_NON_OBJECT + object) }
      if (!owns(object, property)) { return }

      var descriptor, getter, setter
      descriptor = { enumerable: true, configurable: true }
      if (supportsAccessors) {
        var prototype = object.__proto__
        object.__proto__ = prototypeOfObject

        var getter = lookupGetter(object, property)
        var setter = lookupSetter(object, property)
        object.__proto__ = prototype

        if (getter || setter) {
          if (getter) descriptor.get = getter
          if (setter) descriptor.set = setter
          return descriptor
        }
      }
      descriptor.value = object[property]
      return descriptor
    }
  }
  if (!Object.getOwnPropertyNames) {
    Object.getOwnPropertyNames = function getOwnPropertyNames(object) {
      return Object.keys(object)
    }
  }
  if (!Object.create) {
    var createEmpty
    if (Object.prototype.__proto__ === null) {
      createEmpty = function() {
        return { '__proto__': null }
      }
    } else {
      createEmpty = function() {
        var empty = {}
        for (var i in empty) { empty[i] = null }
        empty.constructor =
            empty.hasOwnProperty =
            empty.propertyIsEnumerable =
            empty.isPrototypeOf =
            empty.toLocaleString =
            empty.toString =
            empty.valueOf =
            empty.__proto__ = null
        return empty
      }
    }

    Object.create = function create(prototype, properties) {
      var object
      if (prototype === null) {
        object = createEmpty()
      } else {
        if (typeof prototype !== 'object') { throw new TypeError('typeof prototype[' + (typeof prototype) + "] != 'object'") }
        var Type = function() {}
        Type.prototype = prototype
        object = new Type()
        object.__proto__ = prototype
      }
      if (properties !== void 0) { Object.defineProperties(object, properties) }
      return object
    }
  }
  function doesDefinePropertyWork(object) {
    try {
      Object.defineProperty(object, 'sentinel', {})
      return 'sentinel' in object
    } catch (exception) {
    }
  }
  if (Object.defineProperty) {
    var definePropertyWorksOnObject = doesDefinePropertyWork({})
    var definePropertyWorksOnDom = typeof document === 'undefined' ||
        doesDefinePropertyWork(document.createElement('div'))
    if (!definePropertyWorksOnObject || !definePropertyWorksOnDom) {
      var definePropertyFallback = Object.defineProperty
    }
  }

  if (!Object.defineProperty || definePropertyFallback) {
    var ERR_NON_OBJECT_DESCRIPTOR = 'Property description must be an object: '
    var ERR_NON_OBJECT_TARGET = 'Object.defineProperty called on non-object: '
    var ERR_ACCESSORS_NOT_SUPPORTED = 'getters & setters can not be defined ' +
                                      'on this javascript engine'

    Object.defineProperty = function defineProperty(object, property, descriptor) {
      if ((typeof object !== 'object' && typeof object !== 'function') || object === null) { throw new TypeError(ERR_NON_OBJECT_TARGET + object) }
      if ((typeof descriptor !== 'object' && typeof descriptor !== 'function') || descriptor === null) { throw new TypeError(ERR_NON_OBJECT_DESCRIPTOR + descriptor) }
      if (definePropertyFallback) {
        try {
          return definePropertyFallback.call(Object, object, property, descriptor)
        } catch (exception) {
        }
      }
      if (owns(descriptor, 'value')) {
        if (supportsAccessors && (lookupGetter(object, property) ||
                                      lookupSetter(object, property))) {
          var prototype = object.__proto__
          object.__proto__ = prototypeOfObject
          delete object[property]
          object[property] = descriptor.value
          object.__proto__ = prototype
        } else {
          object[property] = descriptor.value
        }
      } else {
        if (!supportsAccessors) { throw new TypeError(ERR_ACCESSORS_NOT_SUPPORTED) }
        if (owns(descriptor, 'get')) { defineGetter(object, property, descriptor.get) }
        if (owns(descriptor, 'set')) { defineSetter(object, property, descriptor.set) }
      }

      return object
    }
  }
  if (!Object.defineProperties) {
    Object.defineProperties = function defineProperties(object, properties) {
      for (var property in properties) {
        if (owns(properties, property)) { Object.defineProperty(object, property, properties[property]) }
      }
      return object
    }
  }
  if (!Object.seal) {
    Object.seal = function seal(object) {
      return object
    }
  }
  if (!Object.freeze) {
    Object.freeze = function freeze(object) {
      return object
    }
  }
  try {
    Object.freeze(function() {})
  } catch (exception) {
    Object.freeze = (function freeze(freezeObject) {
      return function freeze(object) {
        if (typeof object === 'function') {
          return object
        } else {
          return freezeObject(object)
        }
      }
    })(Object.freeze)
  }
  if (!Object.preventExtensions) {
    Object.preventExtensions = function preventExtensions(object) {
      return object
    }
  }
  if (!Object.isSealed) {
    Object.isSealed = function isSealed(object) {
      return false
    }
  }
  if (!Object.isFrozen) {
    Object.isFrozen = function isFrozen(object) {
      return false
    }
  }
  if (!Object.isExtensible) {
    Object.isExtensible = function isExtensible(object) {
      if (Object(object) === object) {
        throw new TypeError() // TODO message
      }
      var name = ''
      while (owns(object, name)) {
        name += '?'
      }
      object[name] = true
      var returnValue = owns(object, name)
      delete object[name]
      return returnValue
    }
  }
  if (!Object.keys) {
    var hasDontEnumBug = true,
      dontEnums = [
        'toString',
        'toLocaleString',
        'valueOf',
        'hasOwnProperty',
        'isPrototypeOf',
        'propertyIsEnumerable',
        'constructor'
      ],
      dontEnumsLength = dontEnums.length

    for (var key in { 'toString': null }) {
      hasDontEnumBug = false
    }

    Object.keys = function keys(object) {
      if (
        (typeof object !== 'object' && typeof object !== 'function') ||
            object === null
      ) {
        throw new TypeError('Object.keys called on a non-object')
      }

      var keys = []
      for (var name in object) {
        if (owns(object, name)) {
          keys.push(name)
        }
      }

      if (hasDontEnumBug) {
        for (var i = 0, ii = dontEnumsLength; i < ii; i++) {
          var dontEnum = dontEnums[i]
          if (owns(object, dontEnum)) {
            keys.push(dontEnum)
          }
        }
      }
      return keys
    }
  }

  //
  //
  if (!Date.now) {
    Date.now = function now() {
      return new Date().getTime()
    }
  }

  //
  //
  var ws = '\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003' +
    '\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028' +
    '\u2029\uFEFF'
  if (!String.prototype.trim || ws.trim()) {
    ws = '[' + ws + ']'
    var trimBeginRegexp = new RegExp('^' + ws + ws + '*'),
      trimEndRegexp = new RegExp(ws + ws + '*$')
    String.prototype.trim = function trim() {
      return String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, '')
    }
  }

  //
  //
  function toInteger(n) {
    n = +n
    if (n !== n) { // isNaN
      n = 0
    } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
      n = (n > 0 || -1) * Math.floor(Math.abs(n))
    }
    return n
  }

  function isPrimitive(input) {
    var type = typeof input
    return (
      input === null ||
        type === 'undefined' ||
        type === 'boolean' ||
        type === 'number' ||
        type === 'string'
    )
  }

  function toPrimitive(input) {
    var val, valueOf, toString
    if (isPrimitive(input)) {
      return input
    }
    valueOf = input.valueOf
    if (typeof valueOf === 'function') {
      val = valueOf.call(input)
      if (isPrimitive(val)) {
        return val
      }
    }
    toString = input.toString
    if (typeof toString === 'function') {
      val = toString.call(input)
      if (isPrimitive(val)) {
        return val
      }
    }
    throw new TypeError()
  }
  var toObject = function(o) {
    if (o == null) { // this matches both null and undefined
      throw new TypeError("can't convert " + o + ' to object')
    }
    return Object(o)
  }
})
